Deployment

Reverse proxy

Opt-in Caddy or Traefik for TLS termination and routing on plain Docker and Swarm — custom domains, Let's Encrypt, and the socket-proxy security model.

Pin images.proxy when you need HTTPS at the edge on plain docker compose or Docker Swarm. Kubernetes uses cluster Ingress / Gateway API instead — this page is for the compose recipes.

The one rule

Leave the no-proxy default until you need TLS or horizontal scale on compose. Then pick Caddy (simplest automatic HTTPS) or Traefik (label auto-discovery for --scale app=N). Never mount the raw Docker socket into Traefik.

Quick start

Opt in (pick one)

oke.config.ts
images: {
  "store.sql": "postgres:18-alpine",
  // …
  proxy: "caddy:2-alpine", // or "traefik:v3.3"
},

Pin either Caddy or Traefik — never both. Re-run oke docker --prod.

Set the public host and ACME email

docker/.env.docker
OKE_PROXY_HOST=app.example.com
OKE_PROXY_ACME_EMAIL=admin@example.com

.env.docker is preserved across regeneration. Point DNS A/AAAA at the host.

Bring the stack up

cd docker
docker compose -f compose.yml -f compose.store.sql.yml \
  -f compose.proxy.yml -f compose.prod.yml up -d

Pass every -f file oke docker listed. The edge publishes 80/443; app stays on the internal oke network (no host bind on 6530).

Add your public hostname to serve allowedHosts (Security).

Which proxy?

ChoiceBest forHow routing works
Caddy (caddy:2-alpine)hello / minimal / standard — single instance, simplest TLSGenerated Caddyfilereverse_proxy app:6530; automatic HTTPS
Traefik (traefik:v3.3)Horizontally scaled plain composeDocker labels on app; replicas auto-discovered and load-balanced
(omit images.proxy)Local / private networksApp publishes 6530default, unchanged

Consequence: Caddy is the recommended default when you just need a certificate. Choose Traefik when docker compose up --scale app=N is the deployment shape.

Caddy

Emits docker/Caddyfile:

{$OKE_PROXY_HOST:localhost} {
	reverse_proxy app:6530
}
SettingWhat it does
OKE_PROXY_HOSTSite address. localhost → local TLS; a public DNS name → Let's Encrypt HTTP-01
OKE_PROXY_ACME_EMAILWritten into .env.docker for the proxy role. Traefik consumes it; for Caddy, add a global email block if you want a specific ACME contact

Custom domain:

  1. DNS for app.example.com → this host
  2. OKE_PROXY_HOST=app.example.com in docker/.env.docker
  3. Ports 80/443 reachable from the public internet
  4. allowedHosts includes app.example.com

Override the file via compose.override.yml or replace docker/Caddyfileoke docker regenerates the default on the next run.

Traefik

Generated compose.proxy.yml labels app:

LabelMeaning
traefik.enable=trueAdvertise this service to Traefik
traefik.http.routers.app.rule=Host(…)Match OKE_PROXY_HOST (default localhost)
traefik.http.routers.app.entrypoints=websecureHTTPS entrypoint
traefik.http.routers.app.tls.certresolver=letsencryptACME via Let's Encrypt
traefik.http.services.app.loadbalancer.server.port=6530Upstream port on the compose network

HTTP (:80) redirects to HTTPS. ACME email comes from OKE_PROXY_ACME_EMAIL (default admin@example.com) on the Traefik command line.

Scaled replicas need no label edits — docker compose up --scale app=N registers every replica through the Docker provider.

Why docker-socket-proxy (Traefik)

Traefik's Docker provider watches the Engine API for containers and labels. The naive setup mounts /var/run/docker.sock into Traefik itself.

That socket is root-equivalent for the host Docker daemon. If the edge process is compromised (public 80/443), an attacker with the raw socket can start privileged containers, mount the host filesystem, or escape the container.

Traefik documents this risk and recommends a filtered proxy. OKE never mounts docker.sock into Traefik — a companion tecnativa/docker-socket-proxy mounts it read-only and exposes only:

FlagWhy Traefik needs it
CONTAINERSList / inspect for labels
EVENTSWatch start/stop for scale
PINGHealth of the Engine API
VERSIONAPI negotiation
NETWORKSNetwork membership

Destructive Engine calls (images, volumes, exec, swarm admin, …) stay denied. Traefik uses --providers.docker.endpoint=tcp://socket-proxy:2375 on the internal oke network only — do not publish 2375 on the host.

Consequence: a compromised Traefik still talks to a narrow API surface, not the full Docker control plane.

Environment

VariableDefaultUsed by
OKE_PROXY_HOSTlocalhostCaddy site address; Traefik Host() rule
OKE_PROXY_ACME_EMAILadmin@example.comTraefik ACME account email

Where this applies

SurfaceEdge / ingress
DockerOpt-in Caddy or Traefik (images.proxy)
Docker SwarmSame proxy recipes + Swarm routing mesh
KubernetesIngress / Gateway API (cluster-native — not this recipe)

Troubleshooting

Learn more

Next

On this page