Skip to main content

Docker Deployment

note

Use this page when you run GenKitKraft on your own host or on your computer. To try GenKitKraft with one click, see App Platform. To run it on a DigitalOcean server with HTTPS, see DigitalOcean Droplet.

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