CLI Flags & Env Vars
Almost every real Go program needs to read configuration: a port number, a database URL, a debug toggle. Go provides two complementary mechanisms: the flag package for command-line flags, and os for environment variables.
os.Args โ raw command-line
os.Args is a []string containing the program's arguments:
Parsing os.Args manually is error-prone. The flag package does it correctly.
Defining flags
Each call registers a flag and returns a pointer to the value. The pointer is not populated until flag.Parse() is called.
flag.Parse()
Call flag.Parse() once, early in main, before reading any flag value:
After flag.Parse(), dereference the pointers to get the actual values.
Positional arguments
Arguments that aren't flags (don't start with -) are available via flag.Args():
flag.NewFlagSet โ sub-commands
Real CLIs often have sub-commands (git commit, git push). Each sub-command gets its own FlagSet:
Reading environment variables
When you need to distinguish between "variable is missing" and "variable is set to empty string", use os.LookupEnv:
The 12-factor convention
The 12-factor app pattern: flags carry development defaults, environment variables override them in production. A common pattern:
This lets developers run ./myapp with sensible defaults while deployment systems (Docker, Kubernetes) inject configuration via environment variables without touching the binary.
Why flag functions return pointers
Flags are registered before flag.Parse() is called, so the value isn't known at registration time. Returning a pointer lets the flag package write the parsed value into the same memory location after parsing, and the caller always holds a reference to the final value.
Knowledge Check
When must flag.Parse() be called relative to reading flag values?
What is the difference between os.Getenv and os.LookupEnv?
Why do flag.String, flag.Int, and flag.Bool return pointers?