Skip to main content

Docker Deployment

Quick Start​

docker run -d \
--name genkitkraft \
-p 8080:8080 \
-v genkitkraft-data:/data \
-e ENCRYPTION_KEY=$(openssl rand -base64 32) \
-e AUTH_CREDENTIALS=admin:changeme \
--restart unless-stopped \
ghcr.io/deej4y/genkitkraft:latest

For production, use Docker Compose:

services:
genkitkraft:
image: ghcr.io/deej4y/genkitkraft:latest
ports:
- "8080:8080"
volumes:
- genkitkraft-data:/data
environment:
PORT: 8080
DATABASE_PATH: /data/app.db
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
AUTH_CREDENTIALS: ${AUTH_CREDENTIALS}
restart: unless-stopped

volumes:
genkitkraft-data:

Use a .env file for secrets:

ENCRYPTION_KEY=your-generated-key-here
AUTH_CREDENTIALS=admin:strongpassword

Then: docker compose up -d

Persistent Storage​

By default, GenKitKraft stores all data in a SQLite database at /data/app.db. Mount a Docker volume or bind mount to /data to persist data across container restarts.

Multi-instance deployments need two shared backends, not one:

  • A shared database — PostgreSQL, MySQL, or MariaDB via DATABASE_PROVIDER and DATABASE_URL.
  • A shared cache — Redis or Valkey via CACHE_PROVIDER and CACHE_URL. See Shared Cache below.

Both are required. A shared database alone still leaves sessions process-local, so logins will appear to fail at random as requests land on different instances.

See Environment Variables for the full reference.

PostgreSQL​

services:
genkitkraft:
image: ghcr.io/deej4y/genkitkraft:latest
ports:
- "8080:8080"
environment:
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
AUTH_CREDENTIALS: ${AUTH_CREDENTIALS}
DATABASE_PROVIDER: postgres
DATABASE_URL: postgres://genkitkraft:${DB_PASSWORD}@db:5432/genkitkraft?sslmode=disable
depends_on:
db:
condition: service_healthy
restart: unless-stopped

db:
image: postgres:16-alpine
environment:
POSTGRES_USER: genkitkraft
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: genkitkraft
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U genkitkraft"]
interval: 5s
timeout: 5s
retries: 5

volumes:
db-data:

MySQL / MariaDB​

Replace the db service image with mysql:8 or mariadb:11 and set DATABASE_PROVIDER accordingly:

services:
genkitkraft:
image: ghcr.io/deej4y/genkitkraft:latest
ports:
- "8080:8080"
environment:
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
AUTH_CREDENTIALS: ${AUTH_CREDENTIALS}
DATABASE_PROVIDER: mysql # or mariadb
DATABASE_URL: genkitkraft:${DB_PASSWORD}@tcp(db:3306)/genkitkraft?parseTime=true
depends_on:
db:
condition: service_healthy
restart: unless-stopped

db:
image: mysql:8
environment:
MYSQL_USER: genkitkraft
MYSQL_PASSWORD: ${DB_PASSWORD}
MYSQL_DATABASE: genkitkraft
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
volumes:
- db-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 5s
retries: 10

volumes:
db-data:
parseTime=true

The parseTime=true parameter is required in the MySQL/MariaDB DSN for correct timestamp handling.

Shared Cache​

Session tokens, login rate-limit counters, and playground stream-cancellation signals are cached in-process by default. Running more than one instance that way breaks authentication — a login handled by one instance is unknown to the others, so subsequent requests return 401. Stream cancellation degrades more gracefully: "stop generation" still works instantly when it lands on the instance running the stream, and only silently no-ops when it lands on a different one.

Point every instance at one Redis or Valkey server to fix that. Add the service alongside your database:

services:
genkitkraft:
image: ghcr.io/deej4y/genkitkraft:latest
ports:
- "8080:8080"
environment:
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
AUTH_CREDENTIALS: ${AUTH_CREDENTIALS}
DATABASE_PROVIDER: postgres
DATABASE_URL: postgres://genkitkraft:${DB_PASSWORD}@db:5432/genkitkraft?sslmode=disable
CACHE_PROVIDER: valkey # or redis
CACHE_URL: valkey://cache:6379
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
restart: unless-stopped

cache:
image: valkey/valkey:8-alpine
command: ["valkey-server", "--maxmemory", "256mb", "--maxmemory-policy", "noeviction"]
healthcheck:
test: ["CMD", "valkey-cli", "ping"]
interval: 5s
timeout: 5s
retries: 10
restart: unless-stopped

Swap the image for redis:7-alpine (and the healthcheck for redis-cli ping) to use Redis instead — set CACHE_PROVIDER: redis and a redis:// URL. The two are interchangeable.

No volume is mounted: the cache holds no durable data, and losing it only logs users out.

noeviction is deliberate. Every key here already has a TTL — 24h for sessions, 1 minute for rate-limit counters, ~10s for stream-cancellation signals — so volatile-ttl protects nothing, and because it evicts the shortest TTLs first it would drop stream-cancellation signals, then rate-limit counters, before sessions, quietly breaking cross-instance "stop" and resetting brute-force protection under memory pressure. With noeviction a full cache keeps serving reads, so existing sessions stay valid, and fails writes, so new logins return 503 rather than users being silently logged out. See CACHE_PROVIDER for sizing.

Results from the built-in web-fetch tool are not stored here — they stay in a process-local cache, so agent tool traffic cannot exhaust the cache that authentication depends on.

The connection is checked at startup, so a misconfigured CACHE_URL fails immediately rather than after traffic arrives.

Health Checks​

GenKitKraft exposes health check endpoints:

EndpointDescription
GET /livezReturns 200 if the server is running
GET /readyzReturns 200 if the server is ready, 503 if not

Example Docker Compose health check:

healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/readyz"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s

Updating​

docker compose pull
docker compose up -d

Your data is preserved in the named volume.

Building from Source​

If you prefer to build the Docker image yourself:

git clone https://github.com/DEEJ4Y/genkitkraft.git
cd genkitkraft
docker build -t genkitkraft .

The Dockerfile uses a multi-stage build (Node.js for UI → Go for server → Alpine runtime).