Docker Compose guide
The shortest path to a working CapScope: one compose service, one named volume, one password. This guide covers the published image and a plain docker run, then hardens the setup with a read-only socket proxy and shows how to size the database.
Prerequisites
- A Docker host — any machine with the Docker Engine and its socket available.
- A long random password for Basic auth:
openssl rand -base64 24. - Outbound internet on the host if you build from source (the first build downloads Go modules).
- A TLS-terminating reverse proxy in production — CapScope listens on
8080with plain HTTP.
/var/run/docker.sock gives the container full control of the host. Use Basic auth, HTTPS, and preferably the read-only socket proxy below in production.Quick start with docker run
docker run -d --name capscope \
-p 8080:8080 \
-e AUTH_USER=admin \
-e AUTH_PASS='change-me' \
-v capscope-data:/data \
-v /var/run/docker.sock:/var/run/docker.sock \
--restart unless-stopped \
ghcr.io/hacrex/capscope:latest
Open http://localhost:8080 and log in. The named volume capscope-data holds the SQLite database at /data/capscope.db — do not drop it, or history dies with the container.
Docker Compose
services:
capscope:
image: ghcr.io/hacrex/capscope:latest
ports:
- "8080:8080"
environment:
AUTH_USER: admin
AUTH_PASS: change-me
# LOG_RETENTION: 7d
# MAX_DB_MB: 2048
# EXCLUDE: '^captain-'
volumes:
- capscope-data:/data
- /var/run/docker.sock:/var/run/docker.sock
restart: unless-stopped
volumes:
capscope-data:
AUTH_PASS defaults to change-me — change it before exposing the port. The repo also ships deploy/docker-compose.yml, which builds from source (build: ..) instead of pulling the published image:
cd deploy
docker compose up -d --build # then open http://<host>:8080
Harden with a socket proxy
Mounting the socket is the simplest setup, but a read-only socket proxy keeps the raw socket out of CapScope entirely. Add a tecnativa/docker-socket-proxy service that exposes only the read endpoints CapScope needs, drop the socket mount from CapScope, and point DOCKER_HOST at the proxy:
services:
socket-proxy:
image: tecnativa/docker-socket-proxy
environment: { CONTAINERS: 1, EVENTS: 1, PING: 1, POST: 0 }
volumes: ["/var/run/docker.sock:/var/run/docker.sock:ro"]
# capscope: drop the docker.sock mount and set:
# DOCKER_HOST=tcp://socket-proxy:2375
CONTAINERS=1 is what allows GET /containers/* — inspect, logs and stats. POST=0 blocks every write. Do not publish the proxy's port publicly; keep it on the same Docker network as CapScope.
MAX_DB_MB cap drops the oldest logs first. MAX_DB_MB wins over LOG_RETENTION if the two ever conflict.Environment highlights
Everything is configured through environment variables. The full list lives in the configuration reference; these are the ones you almost always touch on a Docker host:
| Variable | Default | Meaning |
|---|---|---|
AUTH_PASS | none | Mandatory. The server refuses to start without it. |
DOCKER_HOST | unix:///var/run/docker.sock | unix:// socket or tcp://host:port for a socket proxy. |
LOG_RETENTION | 7d | Delete logs older than this (d, h, m). |
SNAPSHOT_RETENTION | 30d | Same for crash snapshots. |
MAX_DB_MB | 1024 | Hard cap on database size; oldest data dropped first. |
STATS_INTERVAL | 10s | Metric sample interval per container (min 1s). |
INCLUDE / EXCLUDE | none | Regexes matched against container and app names. |
REDACT | true | Mask JWTs, bearer tokens, AWS keys and PEM keys before storage. |
ALERTS_FILE | /data/alerts.json | Hot-reloaded JSON alert-rules file. |
ALLOW_NO_AUTH | false | Local testing only — never on a shared host. |
Verify
Health endpoint
curl -k http://localhost:8080/healthz returns 200. The path is auth-free by design so proxies and monitors can probe it.
Log in and check the sidebar
Open the UI with Basic auth. The header chip shows a container count above zero, and the sidebar lists your apps within about 10 seconds.
Search a known string
Pick any container, open a log line you expect to exist, and run an FTS5 text search. Results come back with timestamps.
Crash something on purpose
Run a throwaway container that exits with code 1. The Crashes tab shows a snapshot with exit code, log tail and a plain-language summary you can export as Markdown.
Prove the volume works
Restart the CapScope container, then search again — logs, metrics and snapshots survive, which confirms /data is persisted.
Troubleshooting
| Symptom | Cause / fix |
|---|---|
| Container exits instantly with "AUTH_PASS …" | Password not set — set it in the compose file or with -e AUTH_PASS=…. |
| Exits with "cannot reach Docker" | Socket not mounted, or the DOCKER_HOST proxy URL is wrong. |
| Sidebar empty | Socket not mounted / wrong path, or an EXCLUDE regex that is too greedy. |
| Live tail stalls | The reverse proxy is buffering SSE — honor X-Accel-Buffering: no or disable buffering for /api/logs/stream. |
| Data gone after redeploy | /data is not on a persistent volume. |
| Corrupt DB / locked errors | More than one replica writing the same file — scale to exactly 1. |
| Container shows no logs | Its logging driver does not support reading (docker logs fails for it). Supported drivers: json-file, local, journald. |
Capacity
Measured on a sandbox: about 400 bytes of database per stored log line for a 130-byte JSON line (three indexes included). Estimate with bytes per day = lines per second × 86400 × 400:
| Lines per second (all apps) | Per day | Per 7 days |
|---|---|---|
1 | about 35 MB | about 240 MB |
10 | about 350 MB | about 2.4 GB |
100 | about 3.5 GB | about 24 GB |
Metrics are cheap: one row per container every 10 seconds is roughly 8,600 rows per container per day. Set MAX_DB_MB to fit your volume — the oldest logs are dropped early to stay under the cap.
Next: platform-specific steps for CapRover, Coolify, Dokploy, Dokku or Portainer & others.