Чтение конфигурации в Go: короткое руководство
Три способа загрузить настройки в Go-приложение — переменные окружения, YAML-файл и флаги командной строки — с рабочими примерами и проверкой на каждом шаге. Руководство рассчитано на 20–30 минут.

Что понадобится до начала
Прежде чем писать код, убедитесь, что у вас есть:
- Go 1.21 или новее — проверьте командой
go version; - инициализированный модуль — если его нет, выполните
go mod init myappв каталоге проекта; - текстовый редактор или IDE с поддержкой Go (VS Code с расширением Go, GoLand, Vim с gopls — что угодно).
В конце у вас будет структура Config, которая заполняется из трёх источников с понятным приоритетом: флаги > переменные окружения > файл. Этого хватает для большинства CLI-утилит и сервисов.
Шаг 1. Переменные окружения через стандартную библиотеку
Самый простой путь — пакет os. Никаких зависимостей, работает везде.
// main.go
package main
import (
"fmt"
"os"
"strconv"
)
type Config struct {
Host string
Port int
Debug bool
}
func configFromEnv() (Config, error) {
portStr := os.Getenv("APP_PORT")
if portStr == "" {
portStr = "8080" // значение по умолчанию
}
port, err := strconv.Atoi(portStr)
if err != nil {
return Config{}, fmt.Errorf("APP_PORT не является числом: %w", err)
}
host := os.Getenv("APP_HOST")
if host == "" {
host = "localhost"
}
debug, _ := strconv.ParseBool(os.Getenv("APP_DEBUG"))
return Config{Host: host, Port: port, Debug: debug}, nil
}
func main() {
cfg, err := configFromEnv()
if err != nil {
fmt.Println("Ошибка конфигурации:", err)
os.Exit(1)
}
fmt.Printf("host=%s port=%d debug=%v\n", cfg.Host, cfg.Port, cfg.Debug)
}
Проверка. Запустите:
APP_PORT=3000 APP_DEBUG=true go run main.go
В терминале должно появиться: host=localhost port=3000 debug=true. Если вместо этого — ошибка «APP_PORT не является числом», значит переменная содержит нечисловое значение.
Подход работает, но при десятке параметров код разрастается: для каждого поля — Getenv, проверка пустоты, приведение типа. Дальше посмотрим, как это сократить.
Шаг 2. YAML-файл через gopkg.in/yaml.v3
Для сервисов с десятками параметров удобнее хранить настройки в файле. YAML читается проще, чем JSON, и поддерживает комментарии.
2.1. Создайте файл config.yaml
# config.yaml
host: "0.0.0.0"
port: 8080
debug: false
2.2. Установите пакет
go get gopkg.in/yaml.v3
2.3. Добавьте функцию загрузки
// config.go
package main
import (
"fmt"
"os"
"gopkg.in/yaml.v3"
)
// Config хранит параметры приложения.
type Config struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
Debug bool `yaml:"debug"`
}
func configFromFile(path string) (Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return Config{}, fmt.Errorf("не удалось прочитать %s: %w", path, err)
}
var cfg Config
if err := yaml.Unmarshal(data, &cfg); err != nil {
return Config{}, fmt.Errorf("ошибка разбора YAML: %w", err)
}
return cfg, nil
}
Проверка. Допишите вызов в main():
cfg, err := configFromFile("config.yaml")
fmt.Printf("%+v\n", cfg)
Ожидаемый вывод: {Host:0.0.0.0 Port:8080 Debug:false}. Если видите {Host: Port:0 Debug:false} — проверьте, что теги yaml:"..." совпадают с ключами в файле.
Файл — удобная отправная точка, но он не должен быть единственным источником. Значения, которые меняются между средами (порт, адрес БД, секреты), лучше передавать через переменные окружения или флаги — так контейнеры и CI/CD не зависят от содержимого репозитория.
Шаг 3. Флаги командной строки через стандартный flag
Пакет flag из стандартной библиотеки покрывает простые случаи без внешних зависимостей.
package main
import (
"flag"
"fmt"
)
func main() {
host := flag.String("host", "localhost", "адрес, на котором слушать")
port := flag.Int("port", 8080, "порт")
debug := flag.Bool("debug", false, "режим отладки")
flag.Parse()
fmt.Printf("host=%s port=%d debug=%v\n", *host, *port, *debug)
}
Проверка:
go run main.go -host=0.0.0.0 -port=3000 -debug
Вывод: host=0.0.0.0 port=3000 debug=true. Флаг -help выводит описание всех параметров автоматически.
Шаг 4. Собираем вместе — приоритет источников
На практике нужно, чтобы флаг перекрывал переменную окружения, а та — файл. Вот минимальная схема без внешних библиотек:
package main
import (
"flag"
"fmt"
"os"
"strconv"
"gopkg.in/yaml.v3"
)
type Config struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
Debug bool `yaml:"debug"`
}
func loadConfig(path string) (Config, error) {
// 1. Файл — базовые значения
var cfg Config
if data, err := os.ReadFile(path); err == nil {
if err := yaml.Unmarshal(data, &cfg); err != nil {
return cfg, fmt.Errorf("YAML: %w", err)
}
}
// 2. Переменные окружения перекрывают файл
if v := os.Getenv("APP_HOST"); v != "" {
cfg.Host = v
}
if v := os.Getenv("APP_PORT"); v != "" {
p, err := strconv.Atoi(v)
if err != nil {
return cfg, fmt.Errorf("APP_PORT: %w", err)
}
cfg.Port = p
}
if v := os.Getenv("APP_DEBUG"); v != "" {
cfg.Debug, _ = strconv.ParseBool(v)
}
// 3. Флаги перекрывают всё
flag.StringVar(&cfg.Host, "host", cfg.Host, "адрес")
flag.IntVar(&cfg.Port, "port", cfg.Port, "порт")
flag.BoolVar(&cfg.Debug, "debug", cfg.Debug, "отладка")
flag.Parse()
return cfg, nil
}
func main() {
cfg, err := loadConfig("config.yaml")
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Printf("host=%s port=%d debug=%v\n", cfg.Host, cfg.Port, cfg.Debug)
}
Проверка. Создайте config.yaml с port: 9090, затем запустите:
APP_PORT=4000 go run main.go -port=5000
Результат — port=5000: флаг побеждает. Уберите -port=5000 — получите 4000 из окружения. Уберите и переменную — останется 9090 из файла.
Когда этого мало: Viper и альтернативы
Ручная склейка трёх источников терпима при пяти-семи параметрах. Когда их становится больше или нужны фичи вроде hot-reload, вложенных ключей и поддержки нескольких форматов, часто подключают стороннюю библиотеку.
| Библиотека | Что даёт | Цена |
|---|---|---|
| spf13/viper | YAML/TOML/JSON/env/flags, вложенность, watch-файла | Тяжёлая: тянет много зависимостей, медленная компиляция |
| caarlos0/env | Заполнение структуры из переменных окружения по тегам | Лёгкая, но только env — без файлов и флагов |
| knadh/koanf | Модульная: подключаете только нужные провайдеры | Меньше, чем Viper, но API чуть многословнее |
| Стандартная библиотека (os + flag + yaml.v3) | Нулевые лишние зависимости, полный контроль | Ручной код для каждого нового параметра |
Выбор зависит от масштаба проекта. Для микросервиса с десятком параметров стандартной библиотеки с yaml.v3 обычно хватает. Для монолита с динамической перезагрузкой конфигурации смотрите на koanf или Viper.
Сноска. Hot-reload конфигурации — способность приложения перечитать файл настроек без перезапуска процесса. Viper, например, использует для этого fsnotify и вызывает callback при изменении файла. В стандартной библиотеке такого механизма нет — его придётся писать вручную через os.Stat или тот же fsnotify.
Если не получилось
cannot find module providing package gopkg.in/yaml.v3— вы не выполнилиgo get gopkg.in/yaml.v3или находитесь вне модуля. Убедитесь, что в каталоге естьgo.mod.- Все поля структуры нулевые после Unmarshal — теги
yaml:"..."не совпадают с ключами в YAML-файле, или поля структуры не экспортированы (начинаются с маленькой буквы). - Флаг не перекрывает переменную окружения —
flag.Parse()вызывается до чтения env. В примере выше порядок важен: файл → env → flag. Если перепутать, значение по умолчанию изflagзатрёт то, что пришло из окружения. invalid value for flag -port— передано нечисловое значение. Пакетflagсам выводит ошибку и завершает программу с кодом 2.- Файл config.yaml не найден, программа падает — в примере на шаге 4 отсутствие файла не фатально (ошибка
ReadFileмолча пропускается). Если у вас другая логика — проверьте путь и права доступа.
Что дальше
Конфигурация загружена — теперь стоит позаботиться о валидации. Простейший вариант: метод Validate() error на структуре Config, который проверяет диапазон порта (1–65535), непустой хост и прочие инварианты. Вызывайте его сразу после loadConfig — до того, как значения уйдут в остальной код.
Секреты (токены, пароли, ключи БД) не стоит хранить в YAML-файле в репозитории. Передавайте их через переменные окружения или менеджеры секретов (HashiCorp Vault, AWS Secrets Manager, переменные CI/CD). Это отдельная тема, но учтите её на старте, чтобы не переделывать потом.
Проверка грамотности: Орфография, пунктуация, стиль — с объяснением каждой правки
Спросить за 10 ₽Источники: Пакет flag — стандартная библиотека Go, gopkg.in/yaml.v3 — документация пакета
Частые вопросы
Почему не использовать просто JSON вместо YAML?
Обязательно ли соблюдать приоритет «файл → env → флаги»?
А если у меня Go ниже 1.21 — примеры сработают?
Как быть, если приложение не запускается из-за отсутствия обязательного параметра?
Не получилось — код компилируется, но все поля структуры пустые. Что делать?
Можно ли хранить секреты прямо в config.yaml?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.