Putting DockLog behind nginx or Caddy
TLS, WebSocket forwarding, TRUST_PROXY, and the settings we bother with before sharing a URL.
DISABLE_AUTH=true on a home network is fine. Putting the same setup on the internet is not.
DockLog with the Docker socket mounted is effectively root on that host. With a kubeconfig, it's whatever that credential can do. Treat login like infra access, not like sharing a Grafana dashboard.
Baseline compose (auth on, loopback only)
services:
docklog:
image: aimldev/docklog:latest
container_name: docklog
ports:
- "127.0.0.1:8888:8000"
environment:
- SECRET_KEY=${SECRET_KEY}
- DB_PATH=/app/data/docklog.db
- CLIENT_ACCESS=strict
- ENV=production
- TRUST_PROXY=true
- ALLOWED_ORIGINS=https://docklog.example.com
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./data:/app/data
restart: unless-stopped127.0.0.1:8888: only the reverse proxy on the same machine can reach it.
SECRET_KEY: openssl rand -hex 32, keep it in .env, not git.
ALLOWED_ORIGINS: must match the URL people type in the browser, including https://. Login failures behind a proxy are often just a mismatch here plus missing TRUST_PROXY.
Multiple origins (e.g. app and web) are comma-separated:
ALLOWED_ORIGINS=https://docklog.example.com,https://docklog.internal.example.comFull compose walkthrough: docker-compose production setup.
nginx
Log streaming uses WebSockets. Forget the upgrade headers and tails connect then die after a few seconds.
server {
listen 443 ssl http2;
server_name docklog.example.com;
ssl_certificate /etc/letsencrypt/live/docklog.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/docklog.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8888;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
proxy_send_timeout 86400;
}
}That proxy_read_timeout matters. Default 60s kills long tail sessions.
HTTP redirect
server {
listen 80;
server_name docklog.example.com;
return 301 https://$host$request_uri;
}Optional: basic rate limit
Not a substitute for auth, but slows credential stuffing:
limit_req_zone $binary_remote_addr zone=docklog:10m rate=10r/s;
server {
# ... ssl ...
location / {
limit_req zone=docklog burst=20 nodelay;
# ... proxy_pass block ...
}
}Caddy
Less config, same idea:
docklog.example.com {
reverse_proxy 127.0.0.1:8888
}Caddy handles TLS and WebSocket upgrades automatically. Still need TRUST_PROXY=true and ALLOWED_ORIGINS on the DockLog side.
For multiple hostnames:
docklog.example.com, docklog.internal.example.com {
reverse_proxy 127.0.0.1:8888
}Match every hostname in ALLOWED_ORIGINS.
Kubernetes ingress
Same WebSocket mistake at ingress. Annotations vary by controller; nginx ingress example:
metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "86400"
nginx.ingress.kubernetes.io/proxy-send-timeout: "86400"DockLog in-cluster: K8s log tailing.
TRUST_PROXY behavior
When TRUST_PROXY=true, DockLog trusts X-Forwarded-For, X-Forwarded-Proto, and related headers from the proxy.
| Setting | Wrong symptom |
|---|---|
TRUST_PROXY off behind TLS proxy | Cookies/redirects use http:// |
TRUST_PROXY on with public port exposed | Spoofed client IP (bind loopback only) |
ALLOWED_ORIGINS missing https:// | Login loop after successful password |
Only bind DockLog to loopback when a reverse proxy terminates TLS on the same host.
Before you send the link to the team
- HTTPS only, redirect HTTP
- Default admin password changed
- Users created with
allowed_containerspatterns (RBAC post) - Only the
ALLOW_*actions you actually want (ALLOW_SHELLis rarely worth it) - Hit Test on a notification channel if you set up alerts
- Back up
./data/docklog.dbsomewhere off the box - Webhook URLs in notification channels are secrets; rotate if leaked
Optional hardening
| Measure | Notes |
|---|---|
| VPN or Tailscale only | No public URL at all |
| IP allowlist at nginx | allow 203.0.113.0/24; deny all; |
| Separate admin vs read-only users | RBAC patterns, not shared login |
| Audit enabled | DB_PATH set, review periodically |
Official checklist: security hardening guide.
When it misbehaves
| Symptom | Fix |
|---|---|
Login works on :8888 locally, fails on public URL | ALLOWED_ORIGINS or TRUST_PROXY |
| Logs freeze after a minute | Proxy timeout, bump to 86400s |
| 403 on API calls | CLIENT_ACCESS=strict; browser client only |
| Everyone sees every container | User patterns, not proxy issue |
| WebSocket 400/502 | Missing Upgrade headers (nginx) or ingress timeout |
| Mixed content warnings | User bookmarked http://; force HTTPS redirect |
Related reading
Compose walkthrough: docker-compose-production-setup. Official notes: security hardening, reverse proxy. Why socket access still matters: self-hosted log viewer.