# Deployment ## Local development ### Backend ```bash cd backend go mod download go run ./cmd/kereru-forms \ --listen-addr 127.0.0.1 \ --port 8080 \ --log-format console \ --db-dsn "file:./kereru-dev.db?_pragma=busy_timeout(5000)&_pragma=foreign_keys(1)" ``` Useful flags: ```bash kereru-forms --help ``` ### Frontend ```bash cd frontend npm install npm run dev ``` Vite serves on and proxies `/api/*` to `http://localhost:8080`. To point at another API host, set `VITE_API_BASE_URL` at build/dev time: ```bash VITE_API_BASE_URL=http://localhost:8080 npm run dev ``` ## Production build without containers ```bash # Backend binary cd backend CGO_ENABLED=0 go build -ldflags "-s -w -X main.version=1.0.0" -o kereru-forms ./cmd/kereru-forms # Frontend assets cd ../frontend npm ci npm run build ``` Run the binary with the SPA directory and a persistent database: ```bash KERERU_STATIC_DIR=../frontend/dist \ KERERU_DB_DSN="file:/var/lib/kereru/kereru.db?_pragma=busy_timeout(5000)&_pragma=foreign_keys(1)" \ KERERU_LOG_FORMAT=json \ KERERU_ENVIRONMENT=production \ ./kereru-forms --listen-addr 0.0.0.0 --port 8080 ``` The binary serves the API at `/health`, `/forms`, `/submissions` and the SPA for every other path. ## Docker Build from the repository root so both `backend/` and `frontend/` are in context: ```bash docker build -f docker/Dockerfile -t kereru-forms:1.0.0 . docker run -d --name kereru-forms \ -p 8080:8080 \ -v kereru-data:/data \ -e KERERU_CORS_ORIGINS="https://forms.example.com" \ kereru-forms:1.0.0 ``` Or with Compose: ```bash docker compose -f docker/docker-compose.yml up -d --build ``` The image runs as an unprivileged user, stores SQLite data in `/data`, and serves the built frontend from `/app/static`. ## Podman The image is OCI-compatible. Use rootless Podman: ```bash podman build -f docker/Dockerfile -t kereru-forms:1.0.0 . podman run -d --name kereru-forms \ -p 8080:8080 \ -v kereru-data:/data:Z \ -e KERERU_CORS_ORIGINS="*" \ kereru-forms:1.0.0 ``` With Compose (either `podman-compose` or `docker compose` using the Podman socket): ```bash podman-compose -f docker/docker-compose.yml up -d --build ``` `podman generate systemd --new --name kereru-forms` (or a Quadlet unit) can turn the container into a system service. ## Kubernetes 1. Build, tag and push the image to a registry reachable by the cluster: ```bash docker build -f docker/Dockerfile -t registry.example.com/kereru-forms:1.0.0 . docker push registry.example.com/kereru-forms:1.0.0 ``` 2. Update `image:` and the Ingress host in [`deploy/k8s.yaml`](../deploy/k8s.yaml). 3. Apply: ```bash kubectl apply -f deploy/k8s.yaml kubectl -n kereru-forms rollout status deployment/kereru-forms ``` The manifest includes a namespace, config map, 1 GiB `ReadWriteOnce` PVC mounted at `/data`, a `Recreate` deployment (SQLite is single-writer), liveness/readiness probes on `/health`, a service on port 80 and an Ingress. A writable `/tmp` emptyDir is mounted because the container root filesystem is read-only. ### Scaling notes SQLite requires a single writer. Keep `replicas: 1` and use the `Recreate` strategy. To scale horizontally, implement a network database adapter (PostgreSQL/MariaDB) behind `database.Store`, then switch `dbDriver`/`dbDsn` and lift the Deployment restrictions. Use a `ReadWriteMany` volume only if you replace SQLite; do not share a SQLite file across pods. ## Configuration in production Prefer environment variables (twelve-factor) or a mounted YAML/JSON file: ```bash kereru-forms --conf /etc/kereru/config.yaml ``` Typical production settings: - `KERERU_LOG_FORMAT=json` - `KERERU_ENVIRONMENT=production` - `KERERU_CORS_ORIGINS=https://forms.example.com` - `KERERU_TELEMETRY_ENABLED=true` and `OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318` - `KERERU_STATIC_DIR=/app/static` - SQLite DSN on a persistent volume: `file:/data/kereru.db?_pragma=busy_timeout(5000)&_pragma=foreign_keys(1)` - OIDC: `KERERU_OIDC_ISSUER_URL`, `KERERU_OIDC_CLIENT_ID`, `KERERU_OIDC_ROLE_CLAIM`, `KERERU_OIDC_ADMIN_ROLE`, `KERERU_OIDC_VIEW_ROLE` - Optional submission protection: `KERERU_AUTH_REQUIRE_SUBMISSION_AUTH=true` - Local-password mode (only when no OIDC issuer is set): `KERERU_AUTH_LOCAL_PASSWORD` and a stable `KERERU_AUTH_LOCAL_TOKEN_SECRET` ### Backups For SQLite, back up the database file while the service is stopped, or use the SQLite online backup API/`.backup` command. Keep the volume and file permissions owned by the runtime user (UID used in the container image). ### Security - Terminate TLS at the ingress/load balancer. - Management routes require a JWT. Configure OIDC for production and enforce MFA/role assignment in the identity provider. - If using the local password fallback, change `KERERU_AUTH_LOCAL_PASSWORD` from `admin` and set a stable `KERERU_AUTH_LOCAL_TOKEN_SECRET`. - Register the frontend `/auth/callback` URL with the OIDC provider and allow the frontend origin as a web/CORS origin in Okta/Keycloak. - Set `KERERU_CORS_ORIGINS` to the exact frontend origin in production. - Do not expose the SQLite file over a network share.