Backend mobile-development test environment on a single VPS, fronted by a reverse proxy with automatic HTTPS.
| Service | Image | Reachable at |
|---|---|---|
traefik |
traefik:v3.6 | public ports 80 / 443 — reverse proxy, TLS termination, routes every subdomain |
socket-proxy |
tecnativa/docker-socket-proxy:0.3 | internal lets Traefik discover containers without touching the raw Docker socket |
apache |
httpd:2.4 | public babypistachio.com — static site |
gitea |
gitea/gitea:1.22 | public git.babypistachio.com — repository hosting |
jenkins |
jenkins/jenkins:lts-jdk21 | public ci.babypistachio.com — CI/CD |
api |
built from ./api (node:22-alpine) | public api.babypistachio.com/health — placeholder service confirming the environment works end to end |
postgres |
postgres:16 | internal no public route, not yet wired into any service — ready for the real app |
Traefik, apache, gitea, jenkins, and api sit here.
Postgres lives here with no published ports. api is also attached so it can reach postgres:5432 once the real app is deployed, without ever exposing the database externally.
Connects Traefik to docker-socket-proxy only, so Traefik never mounts the host's Docker socket directly.
Five real issues surfaced while bringing this up, each documented in the repo's README so the fix doesn't get silently undone by a future change:
package-lock.json broke the API's Docker build (npm ci requires one); also caught and fixed a moderate qs vulnerability via npm audit fix.socket-proxy crash-looped under read_only: true, which blocked it from writing its own config at startup.chown.api.babypistachio.com returned Gateway Timeout because it sits on two Docker networks and Traefik couldn't infer which one to route through — fixed with an explicit traefik.docker.network label.No hardcoded secrets — everything sensitive comes from .env. Traefik's dashboard is enabled but has no external router, so it isn't reachable from the internet. Security headers (HSTS, X-Frame-Options, nosniff, XSS filter) apply to every public router. The VPS firewall should still restrict inbound traffic to 22/80/443 — Docker network isolation isn't a substitute for that.
Jenkins mounts the host Docker socket directly to support CI/CD builds. Anyone who compromises Jenkins has root-equivalent control of the host. Reasonable trade-off for a test environment, but not something to carry into production as-is — see the README for mitigation options (Docker-in-Docker agents, separate build VMs, rootless Docker).