Running Zentinel and agents in Docker containers.
Quick Start
Run Zentinel
# Run with default configuration
docker run -d \
--name zentinel \
-p 8080:8080 \
-p 9090:9090 \
-v $(pwd)/zentinel.kdl:/etc/zentinel/zentinel.kdl:ro \
ghcr.io/zentinelproxy/zentinel:latest
# Check status
docker logs zentinel
curl http://localhost:9090/health
Run with Agents
# Create shared socket directory
mkdir -p /tmp/zentinel-sockets
# Run WAF agent
docker run -d \
--name waf-agent \
-v /tmp/zentinel-sockets:/var/run/zentinel \
ghcr.io/zentinelproxy/zentinel-agent-waf:latest \
--socket /var/run/zentinel/waf.sock
# Run Zentinel
docker run -d \
--name zentinel \
-p 8080:8080 \
-v $(pwd)/zentinel.kdl:/etc/zentinel/zentinel.kdl:ro \
-v /tmp/zentinel-sockets:/var/run/zentinel \
ghcr.io/zentinelproxy/zentinel:latest
Container Configuration
Environment Variables
docker run -d \
--name zentinel \
-e RUST_LOG=zentinel=info \
-e ZENTINEL_WORKERS=4 \
-e BACKEND_ADDR=api.example.com:443 \
-p 8080:8080 \
-v $(pwd)/zentinel.kdl:/etc/zentinel/zentinel.kdl:ro \
ghcr.io/zentinelproxy/zentinel:latest
Note: If your backend serves HTTPS (port 443), your
zentinel.kdlmust include atlsblock in the upstream configuration. Without it, Zentinel connects with plaintext HTTP, causing 502 errors or redirect loops. See Upstream TLS for details.
Volume Mounts
| Mount | Purpose |
|---|---|
/etc/zentinel/zentinel.kdl | Main configuration |
/etc/zentinel/certs/ | TLS certificates |
/var/run/zentinel/ | Unix sockets for agents |
/var/log/zentinel/ | Log files (optional) |
docker run -d \
--name zentinel \
-v $(pwd)/config:/etc/zentinel:ro \
-v $(pwd)/certs:/etc/zentinel/certs:ro \
-v zentinel-sockets:/var/run/zentinel \
-v zentinel-logs:/var/log/zentinel \
ghcr.io/zentinelproxy/zentinel:latest
Port Mapping
docker run -d \
--name zentinel \
-p 80:8080 \ # HTTP
-p 443:8443 \ # HTTPS
-p 9090:9090 \ # Metrics
ghcr.io/zentinelproxy/zentinel:latest
Networking
Bridge Network (Default)
# Create network
docker network create zentinel-net
# Run containers on network
docker run -d --name backend --network zentinel-net nginx
docker run -d \
--name zentinel \
--network zentinel-net \
-p 8080:8080 \
ghcr.io/zentinelproxy/zentinel:latest
Host Network
For lowest latency (Linux only):
docker run -d \
--name zentinel \
--network host \
ghcr.io/zentinelproxy/zentinel:latest
Connecting to Host Services
# Linux
docker run -d \
--name zentinel \
--add-host=host.docker.internal:host-gateway \
ghcr.io/zentinelproxy/zentinel:latest
# In zentinel.kdl
upstreams {
upstream "backend" {
target "host.docker.internal:3000"
}
}
Running Agents
WAF Agent
docker run -d \
--name waf-agent \
-v zentinel-sockets:/var/run/zentinel \
ghcr.io/zentinelproxy/zentinel-agent-waf:latest \
--socket /var/run/zentinel/waf.sock \
--paranoia-level 1 \
--block-mode true
Auth Agent
docker run -d \
--name auth-agent \
-v zentinel-sockets:/var/run/zentinel \
-e JWT_SECRET="${JWT_SECRET}" \
ghcr.io/zentinelproxy/zentinel-agent-auth:latest \
--socket /var/run/zentinel/auth.sock \
--jwt-issuer api.example.com
Rate Limit Agent
docker run -d \
--name ratelimit-agent \
-v zentinel-sockets:/var/run/zentinel \
ghcr.io/zentinelproxy/zentinel-agent-ratelimit:latest \
--socket /var/run/zentinel/ratelimit.sock \
--requests-per-minute 100
JavaScript Agent
docker run -d \
--name js-agent \
-v zentinel-sockets:/var/run/zentinel \
-v $(pwd)/scripts:/scripts:ro \
ghcr.io/zentinelproxy/zentinel-agent-js:latest \
--socket /var/run/zentinel/js.sock \
--script /scripts/policy.js
Resource Limits
Memory and CPU
docker run -d \
--name zentinel \
--memory=512m \
--memory-reservation=256m \
--cpus=2 \
ghcr.io/zentinelproxy/zentinel:latest
File Descriptors
docker run -d \
--name zentinel \
--ulimit nofile=65536:65536 \
ghcr.io/zentinelproxy/zentinel:latest
Health Checks
Built-in Health Check
docker run -d \
--name zentinel \
--health-cmd="curl -f http://localhost:9090/health || exit 1" \
--health-interval=30s \
--health-timeout=3s \
--health-retries=3 \
--health-start-period=5s \
ghcr.io/zentinelproxy/zentinel:latest
Check Health Status
# View health status
docker inspect --format='{{.State.Health.Status}}' zentinel
# View health logs
docker inspect --format='{{json .State.Health}}' zentinel | jq
Logging
Log Drivers
# JSON file (default)
docker run -d \
--log-driver json-file \
--log-opt max-size=100m \
--log-opt max-file=3 \
--name zentinel \
ghcr.io/zentinelproxy/zentinel:latest
# Syslog
docker run -d \
--log-driver syslog \
--log-opt syslog-address=udp://loghost:514 \
--name zentinel \
ghcr.io/zentinelproxy/zentinel:latest
# Fluentd
docker run -d \
--log-driver fluentd \
--log-opt fluentd-address=localhost:24224 \
--log-opt tag=zentinel \
--name zentinel \
ghcr.io/zentinelproxy/zentinel:latest
View Logs
# Follow logs
docker logs -f zentinel
# Last 100 lines
docker logs --tail 100 zentinel
# With timestamps
docker logs -t zentinel
Security
Read-Only Root Filesystem
docker run -d \
--name zentinel \
--read-only \
--tmpfs /tmp \
-v zentinel-sockets:/var/run/zentinel \
ghcr.io/zentinelproxy/zentinel:latest
Security Options
docker run -d \
--name zentinel \
--security-opt no-new-privileges:true \
--cap-drop ALL \
--cap-add NET_BIND_SERVICE \
ghcr.io/zentinelproxy/zentinel:latest
User Namespace
docker run -d \
--name zentinel \
--userns=host \
--user 1000:1000 \
ghcr.io/zentinelproxy/zentinel:latest
Restart Policies
# Always restart
docker run -d \
--name zentinel \
--restart always \
ghcr.io/zentinelproxy/zentinel:latest
# Restart on failure (max 3 times)
docker run -d \
--name zentinel \
--restart on-failure:3 \
ghcr.io/zentinelproxy/zentinel:latest
# Unless stopped manually
docker run -d \
--name zentinel \
--restart unless-stopped \
ghcr.io/zentinelproxy/zentinel:latest
Configuration Reload
Using Docker Exec
# Reload configuration
docker exec zentinel kill -HUP 1
# Or via admin API
docker exec zentinel curl -X POST http://localhost:9090/admin/reload
Updating Configuration
# Update config file
docker cp zentinel.kdl zentinel:/etc/zentinel/zentinel.kdl
# Reload
docker exec zentinel kill -HUP 1
Debugging
Interactive Shell
# Start shell in running container
docker exec -it zentinel /bin/sh
# Start new container with shell
docker run -it --rm \
-v $(pwd)/zentinel.kdl:/etc/zentinel/zentinel.kdl:ro \
ghcr.io/zentinelproxy/zentinel:latest \
/bin/sh
Inspect Container
# View configuration
docker inspect zentinel
# View mounts
docker inspect --format='{{json .Mounts}}' zentinel | jq
# View network settings
docker inspect --format='{{json .NetworkSettings}}' zentinel | jq
Debug Mode
docker run -d \
--name zentinel \
-e RUST_LOG=zentinel=debug,tower=debug \
-e RUST_BACKTRACE=1 \
ghcr.io/zentinelproxy/zentinel:latest
Production Checklist
Container Configuration
- Resource limits set (memory, CPU)
- File descriptor limits increased
- Health check configured
- Restart policy set
- Logging configured
- Read-only root filesystem (if possible)
Security
- Running as non-root user
- Capabilities dropped
- No new privileges
- Secrets not in environment variables (use Docker secrets)
Networking
- Appropriate network mode selected
- Only required ports exposed
- TLS certificates mounted
Monitoring
- Metrics port exposed
- Log aggregation configured
- Health checks monitored
Next Steps
- Docker Compose - Multi-container orchestration
- Monitoring - Observability setup
- Rolling Updates - Zero-downtime deployments