
이번 글에서는 Go 환경변수에 대해 다루고자 합니다. Go 언어로 실제 프로젝트를 개발하다 보면 데이터베이스 연결 정보, API 키, 서버 포트 등 민감한 정보들을 안전하게 관리해야 하는 상황이 반드시 발생합니다. 이러한 설정값들을 소스코드에 직접 하드코딩하는 것은 보안상 매우 위험하며, 배포 환경에 따라 다른 값을 사용해야 하는 경우에도 비효율적입니다.
환경변수란 무엇인가?
환경변수는 운영체제에서 실행 중인 프로세스가 참조할 수 있는 동적인 값들을 의미합니다. Go 언어에서는 os
패키지를 통해 이러한 환경변수에 접근할 수 있으며, 애플리케이션의 동작을 실행 환경에 따라 유연하게 조절할 수 있습니다.
필자는 처음 Go 언어를 활용하여 웹 사이트를 구성할 때 데이터베이스 비밀번호를 코드에 직접 작성했다가, 실수로 공개 저장소에 업로드한 경험이 있습니다. 이후부터는 반드시 환경변수를 통해 민감한 정보를 관리하는 습관을 들이게 되었습니다.
Go 환경변수 기본 사용법
Go에서 환경변수를 다루는 가장 기본적인 방법은 os.Getenv()
함수를 사용하는 것입니다. 다음 예시를 살펴보겠습니다:
package main
import (
"fmt"
"os"
)
func main() {
// 환경변수 읽기
dbHost := os.Getenv("DB_HOST")
dbPort := os.Getenv("DB_PORT")
if dbHost == "" {
dbHost = "localhost" // 기본값 설정
}
if dbPort == "" {
dbPort = "5432"
}
fmt.Printf("데이터베이스 연결: %s:%s\n", dbHost, dbPort)
}
위 코드에서 os.Getenv()
함수는 지정된 환경변수명에 해당하는 값을 문자열로 반환합니다. 만약 해당 환경변수가 존재하지 않으면 빈 문자열을 반환하므로, 기본값을 설정하는 로직을 포함하는 것이 좋습니다.
환경변수가 설정되어 있는지 확인하려면 os.LookupEnv()
함수를 사용할 수 있습니다:
package main
import (
"fmt"
"os"
)
func main() {
apiKey, exists := os.LookupEnv("API_KEY")
if !exists {
fmt.Println("API_KEY 환경변수가 설정되지 않았습니다.")
return
}
fmt.Printf("API 키: %s\n", apiKey)
}
os.LookupEnv()
함수는 두 개의 값을 반환합니다. 첫 번째는 환경변수의 값이고, 두 번째는 해당 환경변수가 존재하는지를 나타내는 불린값입니다. 이를 통해 환경변수가 빈 문자열로 설정된 경우와 아예 설정되지 않은 경우를 구분할 수 있습니다.
.env 파일을 활용한 환경변수 관리
실제 개발 환경에서는 많은 수의 환경변수를 관리해야 하는 경우가 많습니다. 이때 .env
파일을 활용하면 환경변수들을 체계적으로 관리할 수 있습니다. .env
파일은 키-값 쌍의 형태로 환경변수를 정의하는 텍스트 파일입니다.
먼저 프로젝트 루트 디렉토리에 .env
파일을 생성해보겠습니다:
# 데이터베이스 설정
DB_HOST=localhost
DB_PORT=5432
DB_USER=myuser
DB_PASSWORD=mypassword
DB_NAME=mydb
# 서버 설정
SERVER_PORT=8080
SERVER_HOST=0.0.0.0
# API 설정
API_KEY=your_secret_api_key
DEBUG_MODE=true
Go에서 .env
파일을 읽기 위해서는 외부 라이브러리를 사용하는 것이 일반적입니다. 가장 널리 사용되는 라이브러리는 github.com/joho/godotenv
입니다:
go mod init myproject
go get github.com/joho/godotenv
다음은 godotenv 라이브러리를 사용하여 .env
파일을 로드하고 환경변수를 사용하는 예시입니다:
package main
import (
"fmt"
"log"
"os"
"strconv"
"github.com/joho/godotenv"
)
func main() {
// .env 파일 로드
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
// 환경변수 읽기
dbHost := os.Getenv("DB_HOST")
dbPort := os.Getenv("DB_PORT")
dbUser := os.Getenv("DB_USER")
dbPassword := os.Getenv("DB_PASSWORD")
dbName := os.Getenv("DB_NAME")
serverPort := os.Getenv("SERVER_PORT")
// 불린값 처리
debugMode, _ := strconv.ParseBool(os.Getenv("DEBUG_MODE"))
fmt.Printf("데이터베이스: %s@%s:%s/%s\n", dbUser, dbHost, dbPort, dbName)
fmt.Printf("서버 포트: %s\n", serverPort)
fmt.Printf("디버그 모드: %t\n", debugMode)
}
이 코드에서 godotenv.Load()
함수는 현재 디렉토리의 .env
파일을 찾아서 환경변수로 로드합니다. 파일이 존재하지 않거나 읽기에 실패하면 에러를 반환합니다.
go언어 환경변수 활용 실전 팁
필자가 추후에 gin 웹 프레임워크를 통해 고도화된 사이트를 만들면서 깨달은 환경변수 관리의 핵심은 구조화된 접근 방식입니다. 다음과 같이 설정 구조체를 만들어 환경변수를 체계적으로 관리하는 것을 권장합니다:
package main
import (
"fmt"
"log"
"os"
"strconv"
"time"
"github.com/joho/godotenv"
)
type Config struct {
Database DatabaseConfig
Server ServerConfig
API APIConfig
}
type DatabaseConfig struct {
Host string
Port string
User string
Password string
Name string
}
type ServerConfig struct {
Host string
Port string
ReadTimeout time.Duration
WriteTimeout time.Duration
}
type APIConfig struct {
Key string
DebugMode bool
}
func LoadConfig() (*Config, error) {
// .env 파일 로드 (선택사항)
_ = godotenv.Load()
config := &Config{
Database: DatabaseConfig{
Host: getEnvOrDefault("DB_HOST", "localhost"),
Port: getEnvOrDefault("DB_PORT", "5432"),
User: getEnvOrDefault("DB_USER", "postgres"),
Password: os.Getenv("DB_PASSWORD"), // 필수값
Name: getEnvOrDefault("DB_NAME", "mydb"),
},
Server: ServerConfig{
Host: getEnvOrDefault("SERVER_HOST", "0.0.0.0"),
Port: getEnvOrDefault("SERVER_PORT", "8080"),
ReadTimeout: getDurationEnv("READ_TIMEOUT", 10*time.Second),
WriteTimeout: getDurationEnv("WRITE_TIMEOUT", 10*time.Second),
},
API: APIConfig{
Key: os.Getenv("API_KEY"),
DebugMode: getBoolEnv("DEBUG_MODE", false),
},
}
// 필수 환경변수 검증
if config.Database.Password == "" {
return nil, fmt.Errorf("DB_PASSWORD 환경변수는 필수입니다")
}
if config.API.Key == "" {
return nil, fmt.Errorf("API_KEY 환경변수는 필수입니다")
}
return config, nil
}
func getEnvOrDefault(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
func getBoolEnv(key string, defaultValue bool) bool {
if value := os.Getenv(key); value != "" {
if parsed, err := strconv.ParseBool(value); err == nil {
return parsed
}
}
return defaultValue
}
func getDurationEnv(key string, defaultValue time.Duration) time.Duration {
if value := os.Getenv(key); value != "" {
if parsed, err := time.ParseDuration(value); err == nil {
return parsed
}
}
return defaultValue
}
func main() {
config, err := LoadConfig()
if err != nil {
log.Fatal("설정 로드 실패:", err)
}
fmt.Printf("설정 로드 완료: %+v\n", config)
}
위 코드는 환경변수를 구조화된 방식으로 관리하는 완전한 예시입니다. 각 설정 영역별로 구조체를 나누고, 타입 변환과 기본값 설정을 위한 헬퍼 함수들을 제공합니다. 또한 필수 환경변수에 대한 검증 로직도 포함되어 있습니다.
보안과 배포 환경 고려사항
go언어 환경변수를 사용할 때 가장 중요한 것은 보안입니다. .env
파일은 절대로 버전 관리 시스템에 포함되어서는 안 됩니다. 프로젝트에 .gitignore
파일을 생성하여 다음과 같이 설정하세요:
# 환경변수 파일
.env
.env.local
.env.production
.env.development
# 로그 파일
*.log
# 빌드 결과물
/dist
/build
개발팀과 협업할 때는 .env.example
파일을 만들어 필요한 환경변수들의 템플릿을 제공하는 것이 좋습니다:
# .env.example
DB_HOST=localhost
DB_PORT=5432
DB_USER=your_db_user
DB_PASSWORD=your_db_password
DB_NAME=your_db_name
SERVER_PORT=8080
SERVER_HOST=0.0.0.0
API_KEY=your_api_key_here
DEBUG_MODE=true
프로덕션 환경에서는 .env
파일보다는 실제 시스템 환경변수나 컨테이너 환경변수를 사용하는 것이 더 안전합니다. Docker를 사용하는 경우 docker-compose.yml에서 환경변수를 설정할 수 있습니다:
version: '3.8'
services:
app:
build: .
environment:
- DB_HOST=db
- DB_PORT=5432
- DB_USER=postgres
- DB_PASSWORD=${DB_PASSWORD}
- API_KEY=${API_KEY}
depends_on:
- db
이렇게 Go 환경변수를 체계적으로 관리하면 애플리케이션의 보안성과 유지보수성을 크게 향상시킬 수 있습니다. 특히 실제 서비스를 운영할 때는 환경변수 관리가 매우 중요한 요소가 되므로, 초기 개발 단계부터 올바른 습관을 기르는 것이 중요합니다.
다음 글: Go 테스트 코드 작성법 – 업무에 사용하는 Go 언어 응용편 10 →