Skip to main content

Docker Deployment

Deploy Keyline using Docker and Docker Compose for development, testing, and production environments.

Overview

Keyline provides official Docker images for easy deployment. This guide covers Docker installation, configuration, and best practices.

Quick Start

Pull the Image

docker pull ghcr.io/wasilak/keyline:latest

Basic Docker Run

# Generate required secrets
export SESSION_SECRET=$(openssl rand -base64 32)
export CACHE_ENCRYPTION_KEY=$(openssl rand -base64 32)
export ES_ADMIN_PASSWORD=your-es-admin-password

# Run Keyline
docker run -d \
--name keyline \
-p 9000:9000 \
-v $(pwd)/config.yaml:/etc/keyline/config.yaml \
-e SESSION_SECRET="${SESSION_SECRET}" \
-e CACHE_ENCRYPTION_KEY="${CACHE_ENCRYPTION_KEY}" \
-e ES_ADMIN_PASSWORD="${ES_ADMIN_PASSWORD}" \
ghcr.io/wasilak/keyline:latest \
--config /etc/keyline/config.yaml

Verify Installation

# Check container status
docker ps

# Check health endpoint
curl http://localhost:9000/healthz

# View logs
docker logs keyline

Docker Compose

Basic Setup (Development)

version: '3.8'

services:
keyline:
image: ghcr.io/wasilak/keyline:latest
container_name: keyline
ports:
- "9000:9000"
volumes:
- ./config.yaml:/etc/keyline/config.yaml:ro
environment:
- SESSION_SECRET=${SESSION_SECRET}
- CACHE_ENCRYPTION_KEY=${CACHE_ENCRYPTION_KEY}
- ES_ADMIN_PASSWORD=${ES_ADMIN_PASSWORD}
command: ["--config", "/etc/keyline/config.yaml"]
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:9000/healthz"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
restart: unless-stopped
networks:
- keyline-network

networks:
keyline-network:
driver: bridge

Production Setup (with Redis)

version: '3.8'

services:
keyline:
image: ghcr.io/wasilak/keyline:latest
container_name: keyline
ports:
- "9000:9000"
volumes:
- ./config.yaml:/etc/keyline/config.yaml:ro
environment:
- SESSION_SECRET=${SESSION_SECRET}
- CACHE_ENCRYPTION_KEY=${CACHE_ENCRYPTION_KEY}
- ES_ADMIN_PASSWORD=${ES_ADMIN_PASSWORD}
- REDIS_URL=redis://redis:6379
command: ["--config", "/etc/keyline/config.yaml"]
depends_on:
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:9000/healthz"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
restart: unless-stopped
networks:
- keyline-network
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M

redis:
image: redis:7-alpine
container_name: keyline-redis
command: redis-server --appendonly yes
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
restart: unless-stopped
networks:
- keyline-network

volumes:
redis-data:
driver: local

networks:
keyline-network:
driver: bridge

Full Stack (with Elasticsearch & Kibana)

version: '3.8'

services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:9.3.1
container_name: keyline-es
environment:
- discovery.type=single-node
- xpack.security.enabled=true
- ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
volumes:
- es-data:/usr/share/elasticsearch/data
ports:
- "9200:9200"
networks:
- keyline-network
healthcheck:
test: ["CMD-SHELL", "curl -s http://localhost:9200/_cluster/health | grep -q 'status'"]
interval: 30s
timeout: 10s
retries: 5

kibana:
image: docker.elastic.co/kibana/kibana:9.3.1
container_name: keyline-kibana
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
ports:
- "5601:5601"
depends_on:
elasticsearch:
condition: service_healthy
networks:
- keyline-network

keyline:
image: ghcr.io/wasilak/keyline:latest
container_name: keyline
ports:
- "9000:9000"
volumes:
- ./config.yaml:/etc/keyline/config.yaml:ro
environment:
- SESSION_SECRET=${SESSION_SECRET}
- CACHE_ENCRYPTION_KEY=${CACHE_ENCRYPTION_KEY}
- ES_ADMIN_PASSWORD=${ELASTIC_PASSWORD}
- REDIS_URL=redis://redis:6379
command: ["--config", "/etc/keyline/config.yaml"]
depends_on:
- elasticsearch
- redis
networks:
- keyline-network

redis:
image: redis:7-alpine
container_name: keyline-redis
networks:
- keyline-network

volumes:
es-data:
redis-data:

networks:
keyline-network:
driver: bridge

Configuration

Environment Variables

VariableRequiredDescription
SESSION_SECRETYesSession cookie signing secret (min 32 bytes)
CACHE_ENCRYPTION_KEYYes*Encryption key for credential cache (32 bytes)
ES_ADMIN_PASSWORDYes*Elasticsearch admin password
REDIS_URLNoRedis connection URL (required for production)
OIDC_CLIENT_SECRETNoOIDC client secret (if using OIDC)

*Required when using dynamic user management

Volume Mounts

MountPurpose
./config.yaml:/etc/keyline/config.yaml:roConfiguration file (read-only)
./ssl:/etc/keyline/ssl:roSSL certificates (if using TLS)

Multi-Architecture Support

Keyline images support multiple architectures:

ArchitecturePlatformStatus
amd64Linux, Windows, macOS✅ Supported
arm64Linux (ARM servers), macOS (M1/M2)✅ Supported
arm/v7Raspberry Pi, ARM devices✅ Supported

Build for Specific Architecture

# AMD64
docker pull ghcr.io/wasilak/keyline:latest-linux-amd64

# ARM64
docker pull ghcr.io/wasilak/keyline:latest-linux-arm64

# ARM/v7
docker pull ghcr.io/wasilak/keyline:latest-linux-arm-v7

Security Best Practices

1. Use Read-Only Config

volumes:
- ./config.yaml:/etc/keyline/config.yaml:ro # Read-only

2. Don't Hardcode Secrets

# ❌ BAD: Secrets in compose file
environment:
- SESSION_SECRET=hardcoded-secret

# ✅ GOOD: Use environment variables
environment:
- SESSION_SECRET=${SESSION_SECRET}

3. Use Secrets (Docker Swarm)

version: '3.8'

services:
keyline:
image: ghcr.io/wasilak/keyline:latest
secrets:
- session_secret
- encryption_key

secrets:
session_secret:
external: true
encryption_key:
external: true

4. Network Isolation

networks:
keyline-network:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16

5. Resource Limits

deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M

Monitoring

Health Check

# Check container health
docker inspect --format='{{.State.Health.Status}}' keyline

# Check health endpoint
curl http://localhost:9000/healthz

Logs

# View logs
docker logs keyline

# Follow logs
docker logs -f keyline

# Last 100 lines
docker logs --tail 100 keyline

Metrics

# Access Prometheus metrics
curl http://localhost:9000/_metrics

Troubleshooting

Container Won't Start

Symptoms: Container exits immediately

Solution:

# Check logs
docker logs keyline

# Test configuration
docker run --rm \
-v $(pwd)/config.yaml:/etc/keyline/config.yaml:ro \
ghcr.io/wasilak/keyline:latest \
--validate-config --config /etc/keyline/config.yaml

Can't Connect to Redis

Symptoms: Redis connection errors in logs

Solution:

# Check Redis is running
docker ps | grep redis

# Test connectivity
docker exec keyline wget --spider redis://redis:6379

# Check network
docker network inspect keyline-network

Health Check Fails

Symptoms: Container marked as unhealthy

Solution:

# Check health endpoint manually
docker exec keyline wget -qO- http://localhost:9000/healthz

# Review health check configuration
docker inspect --format='{{.Config.Healthcheck}}' keyline

Next Steps