Endpoints and routing
An endpoint is a tuple: (domain, service, port, tls_mode). One app can declare multiple endpoints. Each endpoint becomes one Caddy route. Caddy receives all inbound traffic on :80 and :443 and matches by Host header.
Declaring endpoints
Section titled “Declaring endpoints”Endpoints are indexed labels on any service in the app. The reconciler reads simpledeploy.endpoints.N.* for N = 0, 1, 2, ...:
services: web: image: ghcr.io/example/web:1.4 ports: ["8080"] labels: simpledeploy.endpoints.0.domain: "app.example.com" simpledeploy.endpoints.0.service: "web" simpledeploy.endpoints.0.port: "8080" simpledeploy.endpoints.0.tls: "auto"
simpledeploy.endpoints.1.domain: "admin.example.com" simpledeploy.endpoints.1.service: "web" simpledeploy.endpoints.1.port: "8080" simpledeploy.endpoints.1.tls: "auto"Two endpoints, both pointing at the same service, served on two different domains.
Upstream resolution
Section titled “Upstream resolution”For each endpoint, /internal/proxy/route.go (resolveEndpointUpstream) picks where Caddy proxies to, in this order:
- If the target service publishes a host port matching
endpoint.port, the upstream islocalhost:<host_port>. - Otherwise, the resolver looks up the service’s container IP on the
simpledeploy-publicnetwork via the Docker API and uses<container_ip>:<port>. Every endpoint-bearing service is auto-attached to this network by the compose mutation step during deploy, so Caddy can reach it whether SimpleDeploy runs natively on Linux (bridge IPs are host-routable) or inside a container on the same network. - As a last-ditch fallback when the Docker API lookup fails (daemon down, container not yet attached), the upstream is the Docker DNS name
<service>:<port>. Only works when Caddy and the target share a network.
You do not need to publish a host port to make an endpoint reachable. Host ports still work and, when present, take precedence (step 1).
TLS modes
Section titled “TLS modes”Set per endpoint via simpledeploy.endpoints.N.tls:
auto(default): Caddy obtains a Let’s Encrypt cert via ACME using the email fromtls.emailin your config.local: Caddy uses its internal CA. Useful for dev/staging without public DNS.custom: Caddy loads<app_dir>/certs/<domain>.crtand.keyfrom disk.off: HTTP only. Caddy’s automatic HTTPS is disabled globally when this is the proxy-wide mode.
The proxy-wide mode comes from your config (tls.mode). Per-endpoint TLS modes layer on top. See TLS guide for the full matrix.
What Caddy actually gets
Section titled “What Caddy actually gets”Routes are built in /internal/proxy/proxy.go (buildConfig) and pushed via caddy.Load() after every reconcile. Each route gets a 4-handler chain in this order:
simpledeploy_ipaccess(allowlist match againstsimpledeploy.access.allow)simpledeploy_ratelimit(per-domain token bucket, configured viasimpledeploy.ratelimit.*)simpledeploy_metrics(records request count, latency, status code into therequest_statstable)reverse_proxyto the resolved upstream