# Environment & configuration Everything the API reads from its environment goes through `config/config.go`. It loads the right `.env` file, reads every setting into one `Config`, and refuses to start — listing *everything* that is wrong in one message — before a single connection is attempted. ## Which file loads `APP_ENV` names the environment. It defaults to `local`. | Command | Files loaded, in order | |----------------------------------|---------------------------------| | `go run .` | `.env.local`, then `.env` | | `APP_ENV=production go run .` | `.env.production`, then `.env` | | `APP_ENV=staging go run .` | `.env.staging`, then `.env` | Precedence, highest first: ``` real environment > .env. > .env ``` A variable that is already set is never overwritten by a file, and no file has to exist. `APP_ENV` itself is read from the real environment before any file is opened — a file cannot decide which file gets loaded. | File | Role | |-------------------|------------------------------------------------------------| | `.env.example` | The complete list of settings, with comments. Start here. | | `.env.local` | The docker-compose stack. Every host is `localhost`. | | `.env.production` | The live hosts. Loaded only when asked for. | | `.env` | Shared base: fallbacks for whatever the file above left out. Keep it local. | ## Running locally ```sh docker compose -f docker-compose.local.yml up -d # postgres :5433, pgvector :5434, redis :6379 go run . # APP_ENV unset → .env.local ``` An empty database is not enough — `main.go` runs migrations that assume the live schema. See `init/README.md` for loading a schema dump first. Startup prints what it loaded and where it is pointed: ``` config: loaded .env.local config: loaded .env config: APP_ENV=local, listening on :1122, database nearle@localhost:5433/nearledb ``` If `DB_HOST` is not a local address under `APP_ENV=local`, it says so: ``` ⚠️ APP_ENV=local but DB_HOST=66.116.x.x is not a local address — every write goes to that database for real ``` That is a warning, not a stop. There is no "local mode" that protects production: `go run .` against the live host creates real tenants and real logins, and runs schema migrations on boot. ## Running in production The container gets **no `.env` file at all** — `.dockerignore` keeps every `.env*` out of the image — and the `Dockerfile` sets `APP_ENV=production`. Every value comes from the platform's environment settings (Dokploy today; ConfigMaps/Secrets under Kubernetes). Under `APP_ENV=production` startup additionally insists on: - `POS_TOKEN_SECRET` (or `JWT_SECRET_KEY` as a fallback), at least 16 characters. A variable added to `.env.production` and not to the platform is a variable that is unset in production. Missing required ones stop the boot with the full list; missing optional ones (`MQTT_URL`, `REDIS_HOST`, `USE_S3`, `CATALOGUE_DB_HOST`) silently disable that subsystem — check the startup log lines when something is "not working". ## What is required | Always | Only when enabled | |----------------------------------------------|---------------------------------------------------------| | `DB_HOST` `DB_USER` `DB_PASSWORD` `DB_NAME` | `CATALOGUE_DB_HOST` set → `CATALOGUE_DB_USER/PASSWORD/NAME` | | (production) `POS_TOKEN_SECRET` | `USE_S3=true` → `S3_ENDPOINT/BUCKET/ACCESS_KEY/SECRET_KEY/REGION` | A half-configured subsystem is an error, not a warning: a warning reads as "fine" in a log and turns into "why are there no images" a week later. ## The committed credentials The three `.env` files, including `.env.production`, are currently tracked in git (commit `be47435`), and an earlier `.env` was committed before 2026-08-03. Every credential in them has to be treated as public: 1. Rotate the database, catalogue, Spaces, MQTT and Redis credentials and the POS signing secret, and update them in the platform. 2. Take the files back out of the index and restore the ignore rules: ```sh git rm --cached .env .env.local .env.production printf '.env\n.env.*\n!.env.example\n' >> .gitignore ``` The files stay on disk; they just stop being committed.