Question
What is the preferred way to manage configuration parameters in a Go program—the kind of settings that might be stored in properties files or INI files in other environments?
Short Answer
By the end of this page, you will understand the common ways to handle configuration in Go, when to use command-line flags, environment variables, or configuration files, and how to structure configuration cleanly in real projects.
Concept
In Go, configuration usually means the values your program needs in order to run correctly, but which should not be hard-coded into the source code.
Examples include:
- Server port numbers
- Database connection strings
- API keys
- Feature flags
- File paths
- Timeouts
A common Go approach is to load configuration into a struct. The values may come from one or more sources:
- Command-line flags using the standard
flagpackage - Environment variables using
os.Getenvoros.LookupEnv - Configuration files such as JSON, YAML, TOML, or INI, usually parsed with a package
Why this matters:
- It keeps code portable across development, testing, and production
- It avoids hard-coding secrets or machine-specific settings
- It makes applications easier to deploy and maintain
- It centralizes setup logic in one place
In real Go programs, there is no single mandatory format for configuration. The preferred approach depends on the app:
- Small CLI tool: flags may be enough
- Web service: environment variables are very common
- Larger app: a config struct loaded from file and/or environment variables is common
The key idea is not the file format itself. The key idea is to define configuration clearly, load it early, validate it, and pass it into the rest of the program.
Mental Model
Think of configuration as the settings panel for your program.
Your code is the machine. Configuration is the set of switches and dials that control how the machine runs in different environments.
- In development, the dial might point to a local database
- In production, the same dial points to a production database
- The machine stays the same, but the settings change
In Go, a struct is like a labeled control board:
type Config struct {
Port string
DBURL string
Debug bool
}
Each field is one setting. Your job is to fill in that control board from flags, environment variables, or files before the rest of the app starts.
Syntax and Examples
A common beginner-friendly pattern is:
- Define a config struct
- Load values into it
- Validate required fields
- Use the config in
main
Example: configuration from environment variables
package main
import (
"fmt"
"os"
)
type Config struct {
Port string
DBURL string
}
func loadConfig() Config {
cfg := Config{
Port: os.Getenv("PORT"),
DBURL: os.Getenv("DATABASE_URL"),
}
if cfg.Port == "" {
cfg.Port = "8080"
}
return cfg
}
func main() {
cfg := loadConfig()
fmt.Println("Port:", cfg.Port)
fmt.Println("Database URL:", cfg.DBURL)
}
What this example shows
Configstores all settings in one placeos.Getenvreads environment variables- A default value is used for
Portif it is not set
Step by Step Execution
Consider this example:
package main
import (
"fmt"
"os"
)
type Config struct {
Port string
}
func loadConfig() Config {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
return Config{Port: port}
}
func main() {
cfg := loadConfig()
fmt.Println("Starting server on port", cfg.Port)
}
Step-by-step
- The program starts in
main(). main()callsloadConfig().- Inside
loadConfig(),os.Getenv("PORT")checks whether thePORTenvironment variable exists. - If
PORTis not set, Go returns an empty string. - The
if port == ""check detects that case.
Real World Use Cases
Configuration appears in almost every Go application.
Web servers
- HTTP port
- TLS certificate paths
- Allowed origins
- Request timeout values
Database-backed apps
- Database host and port
- Username and password
- Connection pool settings
- Migration directory path
CLI tools
- Input and output file paths
- Verbose mode
- Dry-run mode
- Target environment
Background workers
- Queue URL
- Retry count
- Poll interval
- Logging level
API integrations
- API base URL
- API token
- Request timeout
- Rate-limit settings
In all of these cases, hard-coding values would make deployment difficult. Configuration allows the same compiled Go program to run in multiple environments with different settings.
Real Codebase Usage
In real Go codebases, developers usually follow a few practical patterns.
Central config struct
A single struct holds all configuration:
type Config struct {
Port string
DatabaseURL string
Debug bool
TimeoutSecs int
}
This keeps setup organized and avoids scattered config lookups.
Load early in main
A common pattern is:
func main() {
cfg, err := LoadConfig()
if err != nil {
panic(err)
}
app := NewApp(cfg)
app.Run()
}
This ensures the app fails fast if configuration is invalid.
Validation
Required values should be checked immediately:
if cfg.DatabaseURL == "" {
return errors.New("DATABASE_URL is required")
}
This is better than letting the program fail later in a less clear place.
Defaults
Defaults are often applied for optional settings:
Common Mistakes
Here are common beginner mistakes when handling configuration in Go.
1. Scattering config reads all over the code
Broken approach:
func connectDB() {
url := os.Getenv("DATABASE_URL")
// use url
}
func startServer() {
port := os.Getenv("PORT")
// use port
}
Why this is a problem:
- Harder to test
- Harder to find all config requirements
- Repeated logic for defaults and validation
Better approach:
- Load everything once into a
Configstruct - Pass the struct where needed
2. Not validating required values
Broken code:
cfg := Config{
DBURL: os.Getenv("DATABASE_URL"),
}
If DATABASE_URL is missing, the app may fail much later.
Better:
if cfg.DBURL == "" {
return fmt.Errorf()
}
Comparisons
Here is a practical comparison of common configuration sources in Go.
| Approach | Best for | Pros | Cons |
|---|---|---|---|
| Command-line flags | CLI tools, local runs | Built into Go, easy to use, explicit | Less convenient for large config sets |
| Environment variables | Services, containers, deployment | Works well in cloud environments, easy to override | All values start as strings |
| JSON/YAML/TOML/INI files | Larger structured config | Good for many related settings, easier to review | Requires file management and parsing |
| Hard-coded constants | Rarely for true config | Simple for fixed values | Not flexible, poor for deployment |
Flags vs environment variables
| Feature | Flags |
|---|
Cheat Sheet
// Environment variable
value := os.Getenv("PORT")
value, ok := os.LookupEnv("PORT")
// Command-line flag
port := flag.String("port", "8080", "server port")
flag.Parse()
fmt.Println(*port)
// JSON config file
var cfg Config
data, err := os.ReadFile("config.json")
if err != nil {
return err
}
err = json.Unmarshal(data, &cfg)
Good practices
- Define a
Configstruct - Load config once, near program startup
- Apply defaults for optional values
- Validate required values
- Parse values into correct types
- Pass config into functions or components
Common precedence order
- Defaults
- Config file
- Environment variables
- Flags
Useful standard packages
flagfor command-line argumentsosfor environment variables and file readingencoding/jsonfor JSON config filesstrconvfor converting strings tobool, , and more
FAQ
What is the most common way to handle configuration in Go?
A common approach is to load settings into a struct from environment variables, flags, or a config file, then validate and use that struct throughout the app.
Should I use environment variables or config files in Go?
Use environment variables when deploying services or containers. Use config files when you have many structured settings. Many projects support both.
Does Go have a built-in INI or properties file parser?
Not in the standard library. Go includes packages for flags, environment variables, and JSON support, but other file formats usually require external packages.
Where should I load configuration in a Go app?
Usually near the start of main(), before creating the rest of the application.
Should configuration be stored in global variables?
Usually no. It is cleaner and easier to test when configuration is stored in a struct and passed where needed.
How do I set default values in Go configuration?
You can assign defaults when a value is missing, such as using 8080 when PORT is empty.
How do I validate required configuration values?
Check them after loading and return an error if a required field is missing or invalid.
Is it okay to store secrets in config files?
Usually avoid committing secrets to source-controlled config files. Environment variables or dedicated secret managers are safer choices.
Mini Project
Description
Build a small Go application that loads configuration for a web service from environment variables. This demonstrates a practical, common Go pattern: collect settings into a struct, apply defaults, validate required values, and print the final startup settings.
Goal
Create a Go program that reads app settings from the environment, applies defaults, validates required fields, and starts with a clear configuration summary.
Requirements
- Define a
Configstruct with fields for port, app name, debug mode, and database URL. - Load values from environment variables.
- Use default values for optional settings such as port and app name.
- Validate that the database URL is provided.
- Parse the debug value as a boolean.
- Print the loaded configuration or an error message.
Keep learning
Related questions
Blank Identifier Imports in Go: What `_` Means in an Import Statement
Learn what `_` means in a Go import, why blank identifier imports run package init code, and when to use them safely.
Check if a Value Exists in a Slice in Go
Learn how to check whether a value exists in a slice in Go, and why Go has no Python-style `in` operator for arrays or slices.
Concatenating Slices in Go with append
Learn how to concatenate two slices in Go using append and the ... operator, with examples, pitfalls, and practical usage.