Чтение конфигурации в Go — что нужно знать до начала
Переменные окружения, YAML-файлы, флаги командной строки — в Go нет встроенного «прочитай конфиг» из коробки. Разбираемся, какие инструменты есть, чем они отличаются и как не переписывать всё через полгода.

Зачем читать это до первого коммита
В стандартной библиотеке Go нет единого пакета для работы с конфигурацией. Есть os.Getenv, есть flag, есть encoding/json — и каждый решает свою узкую задачу. Новички обычно лепят конфиг из того, что попалось первым, а через полгода переписывают, потому что решение не масштабируется: добавить новый источник конфигурации невозможно без переделки половины main.go.
Эта инструкция — карта перед походом. Вы узнаете, какие источники конфигурации существуют, какие пакеты их читают, и выберете схему до того, как напишете первую строку. Времени — минут двадцать на чтение и час на эксперименты.
Что понадобится
- Go 1.21+ (всё ниже работает и на 1.18, но модули и дженерики уже стандарт де-факто).
- Терминал с доступом к
go runиgo get. - Любой текстовый редактор — VS Code, GoLand, даже nano.
Три источника конфигурации и когда какой нужен
Почти любой Go-сервис получает настройки из одного или нескольких источников. Прежде чем выбирать библиотеку, определитесь с источниками — от этого зависит всё остальное.
Переменные окружения
Самый простой способ. Двенадцатифакторное приложение* рекомендует именно его. Контейнерные оркестраторы (Kubernetes, Docker Compose) передают переменные окружения нативно. Минус — плоская структура: вложенные объекты приходится кодировать через префиксы вроде DB_HOST, DB_PORT.
Файлы (YAML, TOML, JSON, .env)
Удобны для локальной разработки и сложных вложенных структур. YAML и TOML читаются человеком лучше JSON. Файл .env — частный случай: плоский список пар ключ-значение, который удобно подкладывать рядом с docker-compose.yml.
Флаги командной строки
Стандартный пакет flag решает задачу для CLI-утилит. Для сервисов флаги обычно дополняют, а не заменяют другие источники: удобно передать --config=path/to/file.yaml или --debug.
* Двенадцатифакторное приложение (12-Factor App) — методология проектирования SaaS-сервисов, описанная на 12factor.net. Фактор III рекомендует хранить конфигурацию в переменных окружения.
Обзор инструментов
| Инструмент | Источники | Подход | Когда брать |
|---|---|---|---|
os.Getenv + flag (stdlib) | env, флаги | Ручной: сами читаете каждое значение | Микросервис с 3–5 параметрами, никаких зависимостей |
github.com/caarlos0/env | env | Struct tags: описываете структуру, библиотека заполняет | Нужны только переменные окружения, но параметров десятки |
github.com/spf13/viper | env, файлы (JSON, YAML, TOML, HCL, .env), флаги, удалённые хранилища | Всё-в-одном с приоритетами источников | Проект с несколькими средами, сложная структура конфига |
github.com/knadh/koanf | env, файлы, флаги, S3, Consul и др. через провайдеры | Модульный: подключаете только нужные провайдеры | Хотите гибкость Viper без монолитной зависимости |
github.com/joho/godotenv | .env файл | Загружает .env в os.Environ | Локальная разработка; в проде обычно не нужен |
Выбор библиотеки — это выбор уровня абстракции. Если конфиг помещается в пять переменных окружения, Viper — из пушки по воробьям. Если источников три и нужны приоритеты — stdlib превращается в самописный Viper, только хуже.
Шаг 1 — опишите структуру конфигурации
Независимо от инструмента, начните со структуры. Она станет единственным местом правды о параметрах приложения.
// config.go
package config
import "time"
// Config — корневая структура конфигурации приложения.
type Config struct {
Server ServerConfig
Database DatabaseConfig
Debug bool
}
type ServerConfig struct {
Host string
Port int
ReadTimeout time.Duration
}
type DatabaseConfig struct {
DSN string
MaxOpenConns int
ConnMaxLifetime time.Duration
}
Проверка: структура компилируется (go build ./... без ошибок). Пока она не привязана ни к какому источнику — и это правильно.
Шаг 2 — вариант A: только stdlib
Подходит, когда параметров мало и все передаются через окружение.
// main.go
package main
import (
"fmt"
"os"
"strconv"
"time"
)
type Config struct {
Host string
Port int
ReadTimeout time.Duration
DSN string
Debug bool
}
// mustEnv читает переменную или завершает программу с ошибкой.
func mustEnv(key string) string {
val := os.Getenv(key)
if val == "" {
panic(fmt.Sprintf("переменная окружения %s не задана", key))
}
return val
}
func loadConfig() Config {
port, err := strconv.Atoi(mustEnv("APP_PORT"))
if err != nil {
panic(fmt.Sprintf("APP_PORT не число: %v", err))
}
timeout, err := time.ParseDuration(mustEnv("APP_READ_TIMEOUT"))
if err != nil {
panic(fmt.Sprintf("APP_READ_TIMEOUT некорректный: %v", err))
}
return Config{
Host: mustEnv("APP_HOST"),
Port: port,
ReadTimeout: timeout,
DSN: mustEnv("DATABASE_DSN"),
Debug: os.Getenv("APP_DEBUG") == "true",
}
}
func main() {
cfg := loadConfig()
fmt.Printf("Сервер: %s:%d, таймаут: %s\n", cfg.Host, cfg.Port, cfg.ReadTimeout)
}
Запустите так:
APP_HOST=0.0.0.0 APP_PORT=8080 APP_READ_TIMEOUT=5s DATABASE_DSN="postgres://user:pass@localhost/mydb" go run main.go
Проверка: в терминале появится строка Сервер: 0.0.0.0:8080, таймаут: 5s. Если забыли переменную — паника с понятным сообщением.
Шаг 3 — вариант B: struct tags через caarlos0/env
Когда параметров больше десяти, ручной парсинг превращается в копипасту. Библиотека caarlos0/env (v11) заполняет структуру по тегам.
// main.go
package main
import (
"fmt"
"log"
"time"
"github.com/caarlos0/env/v11"
)
type Config struct {
Host string `env:"APP_HOST" envDefault:"localhost"`
Port int `env:"APP_PORT" envDefault:"8080"`
ReadTimeout time.Duration `env:"APP_READ_TIMEOUT" envDefault:"5s"`
DSN string `env:"DATABASE_DSN,required"`
Debug bool `env:"APP_DEBUG" envDefault:"false"`
}
func main() {
var cfg Config
if err := env.Parse(&cfg); err != nil {
log.Fatalf("ошибка чтения конфигурации: %v", err)
}
fmt.Printf("Сервер: %s:%d, debug: %t\n", cfg.Host, cfg.Port, cfg.Debug)
}
Установка:
go get github.com/caarlos0/env/v11
Проверка: без DATABASE_DSN программа завершится с ошибкой required environment variable "DATABASE_DSN" is not set. С корректным значением — выведет параметры. Значения по умолчанию видны прямо в структуре — это документация и код одновременно.
Шаг 4 — добавьте валидацию
Считать значение — половина дела. Порт 99999 или пустой DSN пройдут парсинг, но уронят сервис позже, когда найти причину сложнее. Проверяйте конфиг сразу после чтения.
import "errors"
func (c Config) Validate() error {
if c.Port < 1 || c.Port > 65535 {
return errors.New("порт должен быть в диапазоне 1–65535")
}
if c.ReadTimeout <= 0 {
return errors.New("таймаут чтения должен быть положительным")
}
return nil
}
Вызывайте cfg.Validate() сразу после env.Parse. Ошибка на старте — дешёвая; паника в рантайме — дорогая.
Шаг 5 — приоритеты источников
В реальных проектах часто нужен порядок: флаг командной строки перебивает переменную окружения, а та — значение из файла. Стандартной библиотекой это делается вручную. Если схема усложнилась до трёх источников, присмотритесь к Viper или koanf — они реализуют цепочку приоритетов из коробки. Тонкости настройки этих библиотек — отдельная тема; здесь важно знать, что выбор инструмента определяется именно количеством источников и нужными приоритетами.
Если не получилось
cannot find package "github.com/caarlos0/env/v11"— вы не выполнилиgo get, или модуль не инициализирован. Убедитесь, что в корне естьgo.mod(go mod init myapp), затем повторитеgo get.panic: переменная окружения APP_PORT не задана— переменная не экспортирована. В bash проверьте:export APP_PORT=8080, а не простоAPP_PORT=8080на отдельной строке безexport.APP_READ_TIMEOUT некорректный: time: invalid duration— Go ожидает формат вроде5s,500ms,1m30s. Просто число без суффикса не сработает.- Конфиг читается, но значения пустые — проверьте теги: опечатка в имени переменной внутри
env:"..."молча даёт значение по умолчанию. Добавьте,requiredк обязательным полям — библиотека сразу скажет, чего не хватает.
Что делать дальше
Структура и способ чтения выбраны — теперь стоит закрепить несколько практик:
- Держите конфигурацию в отдельном пакете (
internal/config). Остальной код получает готовую структуру, а не дёргаетos.Getenvиз бизнес-логики. - Для локальной разработки используйте файл
.envиgodotenv— но не тащите его в Docker-образ. В контейнере переменные передаёт оркестратор. - Напишите тест, который вызывает
loadConfigс подставленными переменными черезt.Setenv(доступен с Go 1.17). Это дешевле, чем ловить паники на стейджинге. - Когда источников станет больше двух — мигрируйте на koanf или Viper осознанно, а не потому что «все так делают». Документация koanf: github.com/knadh/koanf.
Субтитры и таймкоды: Расшифровка речи и субтитры по правилам читаемости: 42 знака в строке, две строки, минимум секунда на реплику.
Спросить за 40 ₽Источники: Стандартная библиотека Go — пакет os, koanf — библиотека для чтения конфигурации в Go
Частые вопросы
Какую библиотеку выбрать, если у меня всего пять переменных окружения?
Viper или koanf — в чём принципиальная разница?
Можно ли перечитывать конфиг без перезапуска сервиса?
Секреты (пароли, токены) — тоже через переменные окружения?
А если у коллеги другая версия Go и код не компилируется?
Обязательно ли валидировать конфиг отдельной функцией?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.