This episode covers managing Go application configuration: reading environment variables with os.Getenv, .env files, CLI flags with the flag package, the modern cobra framework, and configuration best practices for local, staging, and production.

Hardcoding configuration values — ports, database credentials, API URLs — is one of the biggest sins in application development. Values that differ between local, staging, and production must be changeable without touching code.
Episode 7 teaches you how to manage configuration idiomatically in Go: reading environment variables, loading .env files during development, handling CLI flags with the built-in flag package, building larger CLIs with cobra, and designing a per-environment configuration strategy. By the end of the episode, your application will be ready to move between environments without changing a single line.
The simplest way to read configuration is environment variables via os.Getenv. A value that isn't set returns an empty string, so always provide a safe default.
package main
import (
"fmt"
"os"
)
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
fmt.Println("server berjalan di port", port)
}To distinguish an unset variable from one set to empty, use os.LookupEnv, which returns two values: the value and its existence status.
Credentials and secrets must never enter the repository. During deployment, environment variables are injected by the platform — Kubernetes through secrets, GitHub Actions through secrets, or the container runtime. Make sure .env and .env.local are in .gitignore.
export DATABASE_URL="postgres://user:pass@localhost:5432/app"
go run main.goIn a local environment, typing a dozen exports every time you open a terminal is very tedious. The common solution: store values in a .env file and load it during development. The popular package for this is github.com/joho/godotenv.
PORT=8080
DATABASE_URL=postgres://user:pass@localhost:5432/app
LOG_LEVEL=debugpackage main
import (
"log"
"os"
"github.com/joho/godotenv"
)
func main() {
if err := godotenv.Load(); err != nil {
log.Println("file .env tidak ditemukan, pakai environment")
}
log.Println("port:", os.Getenv("PORT"))
}godotenv.Load() loads the .env file into the process environment. Don't commit this file to the repository; provide a template in .env.example without secret values.
Set a clear priority order: values already set in the environment win, then the .env file, then defaults in code. With this order, production environments that use environment variables won't be overwritten by wrong defaults.
Go's built-in flag package is enough for simple CLIs. flag.Int, flag.String, and similar functions register flags, and flag.Parse() processes the arguments.
package main
import (
"flag"
"fmt"
)
func main() {
port := flag.Int("port", 8080, "port server")
debug := flag.Bool("debug", false, "mode debug")
flag.Parse()
fmt.Println("port:", *port, "debug:", *debug)
}Run it with go run main.go -port 9090 -debug. Note that flags are stored as pointers, so you read them with the dereference *port.
This combination gives maximum flexibility — pipeline scripts can pass flags, while containers can just use the environment.
port := flag.Int("port", 8080, "port server")
if envPort := os.Getenv("PORT"); envPort != "" {
fmt.Sscanf(envPort, "%d", port)
}For complex CLIs with many subcommands — like kubectl get pods or docker compose up — the cobra package is the industry standard. Cobra handles subcommands, flags, help, and autocomplete in a structured way.
go get github.com/spf13/cobrapackage main
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
func main() {
var nama string
root := &cobra.Command{
Use: "salam",
Short: "CLI sederhana dengan cobra",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Halo,", nama)
},
}
root.Flags().StringVar(&nama, "nama", "dunia", "nama yang disapa")
if err := root.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}Cobra organizes commands, subcommands, and flags in a single tree, with automatically generated help and completion.
Several practices proven in production:
config.local.yaml and config.prod.yaml.With these patterns, teams can move from local to staging to production just by changing environment variables and config files.
Episode 7 equipped you with a comprehensive configuration strategy: reading environment variables with os.Getenv and os.LookupEnv, loading .env files with godotenv for development, handling CLI flags with the flag package, building modern CLIs with cobra, and per-environment configuration best practices.
Key takeaways:
os.LookupEnv distinguishes an empty variable from an unset one..env is only for development and should never be committed.flag package is enough for simple CLIs; cobra for complex ones.In the next episode we will discuss databases, persistence, and data access — database connections with database/sql and popular drivers, lightweight ORMs like sqlc, ent, or gorm, plus data access patterns, parameterized queries, and transaction management.