kereru-forms/docs/DEPLOYMENT.md

4.5 KiB

Deployment

Local development

Backend

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:

kereru-forms --help

Frontend

cd frontend
npm install
npm run dev

Vite serves on http://localhost:5173 and proxies /api/* to http://localhost:8080. To point at another API host, set VITE_API_BASE_URL at build/dev time:

VITE_API_BASE_URL=http://localhost:8080 npm run dev

Production build without containers

# 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:

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:

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:

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:

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):

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:

    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.

  3. Apply:

    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:

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)

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 (/forms, /submissions, /forms/{id}/pdf) are not authenticated; restrict them with an auth proxy or network policy.
  • Set KERERU_CORS_ORIGINS to the exact frontend origin in production.
  • Do not expose the SQLite file over a network share.