Skip to main content

Docker

The key0ai/key0 image is published to Docker Hub on every release. It bundles the Key0 server, the setup UI, and a health check endpoint — everything you need to run Key0 without installing Node or Bun.

Image tags

TagDescription
latestLatest stable release. Safe for production.
1.2.3Exact version pin. Immutable once published.
1.2Latest patch within the 1.2.x line.
1Latest minor and patch within the 1.x.x line.
canaryBuilt from the main branch on every push. Use for testing only.
Pin to a specific semver tag (e.g. key0ai/key0:1.2.3) in production to avoid unexpected upgrades.

Quick start

Run Key0 with a single command. You provide your own Redis and Postgres URLs.
docker run -d \
  --name key0 \
  -p 3000:3000 \
  -e REDIS_URL=redis://your-redis:6379 \
  -e DATABASE_URL=postgresql://user:pass@your-postgres:5432/key0 \
  -v key0-config:/app/config \
  key0ai/key0:latest
The server starts on port 3000. If required configuration is missing, the server redirects to /setup — a browser-based UI where you can configure your wallet address, pricing plans, storage, and other settings. Once saved, the server restarts automatically with your configuration applied. You can revisit /setup at any time to reconfigure.

Compose profiles

Docker Compose profiles let you choose how much infrastructure Key0 manages for you.
CommandWhat startsWhen to use
docker compose upKey0 onlyYou manage Redis and Postgres externally. Set REDIS_URL and DATABASE_URL in .env.
docker compose --profile redis upKey0 + RedisYou manage Postgres externally. Set DATABASE_URL in .env.
docker compose --profile postgres upKey0 + PostgresYou manage Redis externally. Set REDIS_URL in .env.
docker compose --profile full upKey0 + Redis + PostgresEverything managed. No external dependencies needed.
Managed infrastructure is auto-detected at startup via DNS resolution. The KEY0_MANAGED_INFRA environment variable is optional and only needed as an explicit override.

Compose file reference

The full docker-compose.yml ships in the docker/ directory of the repository.
docker-compose.yml
services:
  key0:
    image: key0ai/key0:latest
    ports:
      - "${PORT:-3000}:${PORT:-3000}"
    environment:
      - PORT=${PORT:-3000}
      - KEY0_MANAGED_INFRA=${KEY0_MANAGED_INFRA:-}
      - REDIS_URL=${REDIS_URL:-redis://redis:6379}
      - DATABASE_URL=${DATABASE_URL:-postgresql://key0:key0@postgres:5432/key0}
    env_file:
      - path: .env
        required: false
    extra_hosts:
      - "host.docker.internal:host-gateway"
    volumes:
      - key0-config:/app/config
    depends_on:
      redis:
        condition: service_healthy
        required: false
      postgres:
        condition: service_healthy
        required: false

  redis:
    image: redis:7-alpine
    profiles: [redis, full]
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3
    volumes:
      - redis-data:/data

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

volumes:
  redis-data:
  key0-config:
  postgres-data:

Services

ServiceImagePurpose
key0key0ai/key0:latestKey0 server and setup UI. Listens on ${PORT:-3000}.
redisredis:7-alpineChallenge store and seen-tx store. Activated by redis or full profile.
postgrespostgres:16-alpinePersistent storage and audit log. Activated by postgres or full profile.

Volumes

VolumeMounted atPurpose
key0-config/app/configPersists .env.runtime (seller config written by the setup UI) across container restarts.
redis-data/dataRedis AOF/RDB persistence.
postgres-data/var/lib/postgresql/dataPostgres data directory.
To reset all persisted state and start fresh:
docker compose down -v

Environment variables

Pass environment variables via -e flags, an env_file, or shell exports that Docker Compose reads automatically.
VariableDefaultDescription
PORT3000Port the Key0 server listens on.
REDIS_URLredis://redis:6379Redis connection string. Points to the managed Redis service by default.
DATABASE_URLpostgresql://key0:key0@postgres:5432/key0Postgres connection string. Points to the managed Postgres service by default.
POSTGRES_USERkey0Username for the managed Postgres instance.
POSTGRES_PASSWORDkey0Password for the managed Postgres instance.
POSTGRES_DBkey0Database name for the managed Postgres instance.
KEY0_MANAGED_INFRA(auto-detected)Explicit override for managed infrastructure detection. Normally not needed.
For the full list of Key0 application-level environment variables (wallet address, network, JWT secrets, etc.), see the Environment Variables page.

Health check

The Key0 container includes a built-in health check that polls GET /health every 30 seconds.
# Check container health status
docker inspect --format='{{.State.Health.Status}}' key0
You can also call the endpoint directly:
curl http://localhost:3000/health

Building from source

To build the image locally instead of pulling from Docker Hub:
git clone https://github.com/key0ai/key0.git
cd key0
docker build -t key0ai/key0 .
The Dockerfile uses a multi-stage build. The first stage compiles the setup UI, and the second stage installs dependencies and copies the server source.

Networking tips

The Compose file maps host.docker.internal to the host gateway. This lets the Key0 container reach services running on your host machine (e.g. a local Redis or Postgres instance) using host.docker.internal as the hostname.
# Example: Key0 in Docker, Redis on host machine
docker run -d \
  --add-host=host.docker.internal:host-gateway \
  -e REDIS_URL=redis://host.docker.internal:6379 \
  -p 3000:3000 \
  key0ai/key0:latest