Deploying with Docker Compose¶
Using Docker Compose is the officially recommended path for production deployments not running on a Kubernetes cluster. Keystone provides a starter compose recipe for launching the complete application stack from a single command.
Danger
Keystone deploys with insecure defaults that are not suitable for production. See the Settings page for a complete overview of configurable options and recommended settings.
Deployment Overview¶
An official compose recipe is provided below along with a table summerizing
the deployed services. The recipe assumes application settings are defined
in various .env files located in the same directory as the compose file.
Example content for these files is also provided. For a full list of
available API settings, see the Settings page.
| Service | Exposed Ports | Description |
|---|---|---|
proxy |
80 |
Nginx reverse proxy for routing incoming requests and terminating TLS. |
web |
Frontend service running the Keystone web interface. | |
api |
8000 |
Backend API responsible for business logic and database access. |
db |
PostgreSQL database used for persistent application data. | |
cache |
Redis instance used as a caching and messaging backend for Celery components. | |
smtp |
8025 |
A mock SMTP server with a web frontend for viewing issued emails. |
scheduler |
Celery Beat scheduler for executing periodic tasks. | |
worker |
Celery worker processes for background task execution (4 replicas by default). |
docker-compose.yml
name: keystone
services:
proxy:
image: nginx:alpine3.22
container_name: keystone-proxy
restart: unless-stopped
depends_on:
api:
condition: service_started
web:
condition: service_started
ports:
- "80:80"
networks:
- frontend
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- uploaded_files:/app/media
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost" ]
interval: 10s
timeout: 10s
retries: 5
web:
image: docker.cloudsmith.io/better-hpc/keystone/keystone-web:${API_VERSION:-latest}
container_name: keystone-web
restart: unless-stopped
depends_on:
api:
condition: service_started
networks:
- backend
- frontend
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost" ]
interval: 10s
timeout: 10s
retries: 5
api:
image: docker.cloudsmith.io/better-hpc/keystone/keystone-api:${API_VERSION:-latest}
container_name: keystone-api
restart: unless-stopped
entrypoint: |
sh -c "
keystone-api migrate --no-input # Apply latest DB schema
uvicorn --host 0.0.0.0 --port 8000 --workers 4 keystone_api.main.asgi:application
"
env_file:
- api.env
depends_on:
cache:
condition: service_healthy
db:
condition: service_healthy
smtp:
condition: service_healthy
scheduler:
condition: service_started
ports:
# API Server
- "8000:8000"
# Prometheus Metrics
- "9101:9101"
- "9102:9102"
- "9103:9103"
- "9104:9104"
networks:
- backend
volumes:
- uploaded_files:/app/upload_files
scheduler:
image: docker.cloudsmith.io/better-hpc/keystone/keystone-api:${API_VERSION:-latest}
container_name: keystone-scheduler
restart: unless-stopped
entrypoint: celery -A keystone_api.apps.scheduler beat --scheduler django_celery_beat.schedulers:DatabaseScheduler
env_file:
- api.env
depends_on:
cache:
condition: service_healthy
db:
condition: service_healthy
worker:
condition: service_healthy
networks:
- backend
healthcheck:
disable: true
worker:
image: docker.cloudsmith.io/better-hpc/keystone/keystone-api:${API_VERSION:-latest}
restart: unless-stopped
entrypoint: celery -A keystone_api.apps.scheduler worker
env_file:
- api.env
depends_on:
cache:
condition: service_healthy
db:
condition: service_healthy
smtp:
condition: service_healthy
networks:
- backend
deploy:
mode: replicated
replicas: 4
healthcheck:
test: [ "CMD-SHELL", "celery -A keystone_api.apps.scheduler inspect ping --destination celery@$$HOSTNAME" ]
interval: 10s
timeout: 10s
retries: 5
smtp:
image: axllent/mailpit
container_name: keystone-smtp
restart: unless-stopped
environment:
MP_MAX_MESSAGES: 5000
MP_DATABASE: /data/mailpit.db
MP_SMTP_AUTH_ACCEPT_ANY: 1
MP_SMTP_AUTH_ALLOW_INSECURE: 1
networks:
- backend
ports:
- "8025:8025"
- "1025:1025"
volumes:
- smtp_data:/data
healthcheck:
test: [ "CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:8025/readyz" ]
interval: 10s
timeout: 10s
retries: 5
cache:
image: redis:8.0.2
container_name: keystone-cache
restart: unless-stopped
command: redis-server
networks:
- backend
volumes:
- cache_data:/data
healthcheck:
test: [ "CMD", "redis-cli", "ping" ]
interval: 10s
timeout: 3s
retries: 5
db:
image: postgres:17.4
container_name: keystone-db
restart: unless-stopped
env_file:
- db.env
networks:
- backend
volumes:
- postgres_data:/var/lib/postgresql/data/
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB" ]
interval: 10s
timeout: 10s
retries: 5
volumes:
uploaded_files:
name: keystone_uploaded_files
postgres_data:
name: keystone_postgres_data
cache_data:
name: keystone_cache_data
smtp_data:
name: keystone_smtp_data
networks:
frontend:
name: keystone_frontend
backend:
name: keystone_backend
api.env
DJANGO_SETTINGS_MODULE="keystone_api.main.settings"
STORAGE_STATIC_DIR="/app/static"
STORAGE_UPLOAD_DIR="/app/upload_files"
# Security Settings
SECURE_SECRET_KEY="e5D+WcNeFEx363j3K9YUgypEGWgfK+vS5pgYsAX71ZcSDAAewL"
SECURE_ALLOWED_HOSTS="127.0.0.1,localhost,keystone-api"
SECURE_ALLOWED_ORIGINS="http://127.0.0.1,http://localhost,http://keystone-web,http://keystone-proxy"
SECURE_CSRF_ORIGINS="http://127.0.0.1,http://localhost,http://keystone-web,http://keystone-proxy"
# Redis settings
REDIS_HOST="cache"
# Database settings
DB_POSTGRES_ENABLE="true"
DB_NAME="keystone"
DB_USER="db_user"
DB_PASSWORD="foobar123"
DB_HOST="db"
# SMTP settings
EMAIL_HOST="smtp"
EMAIL_PORT="1025"
db.env
Deploying the Stack¶
The recipe reads settings from two environment files.
The api.env file is used to configure settings for the Keystone API.
The db.env file is used to define settings for the backend postgres database.
Matching credentials
The database credentials in api.env and db.env must match exactly, or
the API will fail to connect to the database. The REDIS_HOST and DB_HOST
values must also match the corresponding service names in the compose file.
To launch the deployment, run the docker compose up command from the directory
same containing the compose recipe and .env files:
After the application launches, create a new administrative user
by calling the keystone-api createsuperuser command from within
the API container:
The web interface is now available at http://localhost:80.
Confirm the deployment by navigating to the site and authenticating with the
newly created credentials.