Skip to content

Security

Network perimeter

Only three ports are open on the VPS firewall (ufw):

Port Purpose
22 SSH (key-only, root disabled)
80 HTTP — used only for Let's Encrypt HTTP-01 challenge, then redirects to HTTPS
443 HTTPS — all public traffic via Traefik

All other ports are blocked. Internal services (Prometheus, cAdvisor, Loki, Prometheus) are bound to 127.0.0.1 loopback on the VPS and are not reachable from the internet.

Auth-gated admin UIs

Service Protection
Traefik dashboard Traefik basicauth middleware (TRAEFIK_DASHBOARD_AUTH)
Portainer Portainer own login (set on first use)
Grafana Grafana login (GF_AUTH_ANONYMOUS_ENABLED=false)
MinIO console MinIO login (MINIO_ROOT_USER / MINIO_ROOT_PASSWORD)

Secrets model (three zones)

Zone 1 — Local
  infrastructure/.env     (gitignored, set manually)
  projects/<name>/.env    (gitignored, set manually)

Zone 2 — GitHub Actions
  VPS_SSH_KEY, VPS_HOST, VPS_USER
  (repo secrets, used only by CI/CD workflows)

Zone 3 — VPS
  ~/data-lab/infrastructure/.env     (set directly on VPS, never synced from repo)
  ~/ingest/.env, ~/demo/.env         (set directly on VPS)

.env.example files commit only variable names (empty values). Real values never enter git.

Docker socket

Traefik and Portainer mount /var/run/docker.sock:ro (read-only). A full socket proxy (CP8) is planned to further restrict which Docker API endpoints each consumer can reach.

TLS

Let's Encrypt certificates are issued per-hostname via HTTP-01 challenge. acme.json is stored in a named Docker volume (letsencrypt), permissions 600 root:root.

Image sourcing

All images are pinned to specific release tags (no latest). Project images are public on GHCR — no registry credentials needed on the VPS.