๐ŸšฉCLI Flags & Env VarsLESSON

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?