Stage 7 · Master
Phase 1 — Project Foundation
Environment Variables
Load .env for local development without letting it silently outrank a real environment variable set by CI, a container, or a teammate's shell.
Why Precedence Order Is a Correctness Issue
A .env file is a convenience for local development: it lets a contributor set ORGANIZATION_PORT once in a file instead of exporting it in every new terminal tab. The moment that convenience is allowed to override a variable a deployment platform, a CI runner, or a Kubernetes ConfigMap already set, it stops being a convenience and becomes a source of an entire class of 'works on my machine, breaks in staging' bugs. The rule this lesson enforces is simple and non-negotiable: a real environment variable always wins over a .env file, and a .env file always wins over a struct default.
Loading .env in Exactly One Place
// Load local defaults before config.Load reads the process environment.
// Load does not overwrite keys already exported by the shell or runtime.
_ = godotenv.Load()
The rest of main.go is unchanged and is not printed again. godotenv.Load is called exactly once before config.Load—never inside internal/config or a test helper—so precedence has one visible owner.
godotenv's Load function sets a key from the .env file only if that key is not already present in the process environment. This is exactly the precedence this lesson needs: export ORGANIZATION_PORT=7070 in a shell before running go run ./cmd/api, and the .env file's ORGANIZATION_PORT is ignored for that run — no extra code required.
.env.example as Executable Documentation
# Copy to .env and adjust for local development.
# Real environment variables (CI, containers, Kubernetes) always take
# precedence over this file — see the Environment Variables lesson.
ORGANIZATION_ENV=development
ORGANIZATION_PORT=8080
What CI and Containers Must Never Do
Neither a GitHub Actions runner nor the Dockerfile built in Phase 2 ever ships a .env file into its environment. Both set the real variables they need directly (as workflow env: keys, or as Kubernetes ConfigMap-sourced environment variables) and rely on godotenv.Load's no-op behavior when no .env file is present. Copying .env.example into .env inside a CI pipeline, thinking it 'documents' the defaults, would instead reintroduce exactly the precedence risk this lesson exists to prevent, if any real variable were ever misconfigured to be absent.
Proving Precedence, Not Just Believing It
A line like PORT 8080 (missing the =) makes godotenv.Load return a parse error. Because that error is discarded with _ = godotenv.Load(), a broken .env file today silently falls through to real environment variables and struct defaults instead of crashing the service — acceptable for a local convenience file, but worth knowing: a bad .env fails silently, not loudly, and struct defaults from the Configuration Management lesson are the safety net.
Applied exercise
Prove that a missing .env file is not treated as an error
The main.go change ignores godotenv.Load's return value. Confirm that decision is intentional and safe, not an oversight.
- Delete the local .env file entirely (mv it aside, don't just clear it).
- Run go run ./cmd/api and confirm the service starts using struct defaults from Configuration Management.
- Temporarily change the ignored error to log.Printf("godotenv: %v", err) and rerun without a .env file to see the exact message godotenv produces for a missing file.
- Revert the temporary logging change — production behavior stays silent on a missing .env, as documented in this lesson.
Deliverable
A short note recording the exact log line godotenv produces for a missing .env file, and confirmation the service still starts.
Completion checks
- The service starts successfully with no .env file present.
- The temporary debug log line is reverted before finishing — main.go matches this lesson's version exactly.
If both a .env file and a real OS environment variable set ORGANIZATION_PORT to different values, which one does the running service use?
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.