# waitfordb A tiny, stateless Go tool that blocks until a database is ready, then exits `0`. It is designed to run as a Kubernetes **initContainer** so an app never starts before its database can serve queries. It replaces the hand-written `psql`-in-a-shell initContainers we used on the arrstack (sonarr/radarr/prowlarr). Readiness means a trivial liveness query (`SELECT 1`) succeeds under the given credentials and database — proving the **server is up, auth works, and the target database/role exist**. On timeout it exits non-zero so the pod fails fast and Kubernetes restarts it. `postgres` is the default driver; `mysql` is also supported. Configuration is entirely via environment variables. ## Environment variables | Variable | Default | Description | | --------------------------- | ------------ | ------------------------------------------------------------------ | | `WAITFORDB_DRIVER` | `postgres` | Database driver: `postgres` or `mysql`. | | `WAITFORDB_HOST` | `localhost` | Database host. Postgres falls back to `PGHOST`. | | `WAITFORDB_PORT` | `5432`/`3306`| Database port (driver default). Postgres falls back to `PGPORT`. | | `WAITFORDB_USER` | — | Username. Postgres falls back to `PGUSER`. Required (unless DSN). | | `WAITFORDB_PASSWORD` | — | Password. Postgres falls back to `PGPASSWORD`. | | `WAITFORDB_DATABASE` | — | Database name. Postgres falls back to `PGDATABASE`. Required (unless DSN). | | `WAITFORDB_SSLMODE` | driver default | Postgres sslmode (e.g. `disable`). Falls back to `PGSSLMODE`. | | `WAITFORDB_DSN` | — | Full driver-native connection string. Overrides all fields above. | | `WAITFORDB_TIMEOUT` | `0` (forever)| Total wait budget as a Go duration (e.g. `5m`). `0` waits forever. | | `WAITFORDB_INTERVAL` | `2s` | Gap between retries. | | `WAITFORDB_CONNECT_TIMEOUT` | `5s` | Per-attempt connect timeout, so a black-holed host cannot hang. | **Precedence for connection parameters:** `WAITFORDB_DSN` > `WAITFORDB_*` > `PG*`. The `PG*` (libpq) fallback applies to the **postgres** driver only, so the tool is a drop-in replacement anywhere those variables are already set. ### Exit codes | Code | Meaning | | ---- | ------------------------------------------- | | `0` | Database ready. | | `1` | Timed out (or interrupted by SIGTERM/SIGINT). | | `2` | Configuration error. | `waitfordb version` prints the version; `waitfordb help` prints usage. ### Example logs ``` 12:49:50 waitfordb v0.1.0: waiting for database (driver=postgres addr=db:5432 database=sonarr-main user=sonarr password=*** timeout=5m interval=2s connect_timeout=5s) 12:49:52 attempt 1: failed to connect to `user=sonarr database=sonarr-main`: dial tcp ... connection refused; retrying in 2s (elapsed 2s/5m) 12:49:56 database sonarr-main ready after 4.2s (3 attempts) ``` The password is never printed — not in the startup line, not in any error line. ## Kubernetes initContainer example Drop this initContainer into a Deployment/StatefulSet. It reuses the same `Secret` the app consumes, so there is no second place to keep credentials. ```yaml initContainers: - name: wait-for-db image: artifactapi.k8s.syd1.au.unkin.net/docker-internal/waitfordb:latest env: - name: WAITFORDB_HOST value: arrstack-postgres-rw.arrstack.svc.cluster.local - name: WAITFORDB_PORT value: "5432" - name: WAITFORDB_DATABASE value: sonarr-main - name: WAITFORDB_SSLMODE value: disable - name: WAITFORDB_TIMEOUT value: 5m - name: WAITFORDB_USER valueFrom: secretKeyRef: name: sonarr-db key: username - name: WAITFORDB_PASSWORD valueFrom: secretKeyRef: name: sonarr-db key: password resources: requests: cpu: 10m memory: 16Mi limits: cpu: 100m memory: 64Mi securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsNonRoot: true capabilities: drop: ["ALL"] ``` The libpq fallback means you can instead pass a single set of `PG*` variables (e.g. via `envFrom` a shared ConfigMap/Secret) with no `WAITFORDB_*` at all. ## Development ```sh make build # build ./dist/waitfordb make test # unit tests (fake driver + fake clock, deterministic) make test-integration # spins up postgres:16 via podman, asserts wait-then-succeed make docker # build the container image locally ``` Releases are cut by tagging: `make patch|minor|major` bumps the semver tag and pushes it, which triggers the Woodpecker pipeline to build and push `artifactapi.k8s.syd1.au.unkin.net/docker-internal/waitfordb:` (and `latest`). ## Adding a driver Implement `driver.Driver` (`Name()` + `Open(config.Config) (Pinger, error)`) and register it from an `init()`. Engines that plug into `database/sql` can reuse the shared `sqlPinger` (see `internal/driver/postgres.go` and `mysql.go`).