Skip to main content

Reverse proxy

Status: available, opt-in. sol2docker can run a managed reverse proxy so you can put a domain in front of a service or container — with automatic TLS, WebSocket/TCP/UDP support, client certificates, and behavioral blocking — all from the Reverse proxy page. Nothing about a specific site is hand-edited into an nginx file; you describe routes in the UI and the proxy applies them live, with zero downtime.

The proxy is a separate OpenResty image, shipped as ghcr.io/sol2docker/proxy. It is a dumb data plane: it pulls a versioned config bundle from sol2docker over an outbound-only, per-environment token and serves it. All the product logic — what routes exist, which certificates, ACME, bans — lives in sol2docker. The proxy token is data-plane only: it can pull that one environment's config and report bans, and can never drive Docker.

Why a managed proxy

Exposing a service normally means hand-writing nginx/Traefik config outside the product, managing certificates by hand, and reloading with a blip of downtime. The managed proxy folds all of that into sol2docker:

  • Domain → service/container mapping from the UI, on both standalone daemons and Swarm.
  • TLS handled for you — issue and auto-renew via Let's Encrypt, or upload your own.
  • Zero-downtime changes — a route, certificate, or ban edit hot-applies on the proxy's next poll; the proxy is never reloaded and never blanks.
  • Survives restarts — the last-known-good config is persisted to a volume, so a restarted proxy serves immediately, before it has re-synced.

Deploy the proxy

From the Reverse proxy page, choose Deploy proxy. sol2docker holds the Docker socket, so it does the setup for you:

  1. Creates the shared network sol2docker-proxy (an attachable overlay on Swarm, a bridge on a standalone daemon). Attach the services/containers you want to route to this network.
  2. Checks that ports 80 and 443 are free on the target (a preflight; it refuses with a clear error, naming the conflicting service/container, rather than failing to bind).
  3. Deploys the proxy as stack sol2docker (so the service is sol2docker_proxy), mounting a named volume for its persisted state.
SurfaceHow it runsWhy
SwarmHost-mode, global (one replica per node) on :80/:443.Host mode preserves the real client IP (so blocking works) and any replica can answer an ACME challenge.
StandalonePublished 80:80 / 443:443, restart: unless-stopped.A single daemon terminating traffic directly.

You can also copy the generated compose and deploy it yourself — the modal shows it. Either way the service is labeled sol2docker.role=proxy, so it's badged Proxy wherever it appears in the Stacks/Services/Containers lists (it stays fully manageable from the Reverse proxy → Deployment tab too).

Routing

Add routes from the Routes tab (L7) — or the deployment's Routes. A route forwards matched traffic to a target, which can be a Swarm service, a container, or a plain address; the target must share the sol2docker-proxy network with the proxy.

SchemeLayerMatches onNotes
HTTP / HTTPSL7Hostname + path prefixVirtual-host routing; optional path stripping.
WebSocketL7Hostname + path prefixUpgrade handshakes are passed through (can be disabled per route).
TCP / UDPL4A published listen portRaw stream pass-through to the target port.

Service and container targets are addressed by name (resolved through Docker's embedded DNS), so a Swarm VIP moving or a container restarting doesn't strand the route. Multiple backends are load- balanced with passive health — a failing backend is skipped until it recovers.

TLS

Set a route's TLS mode when you create it, and manage certificates from the Certificates tab.

ModeWhat it does
NonePlain HTTP.
Uploaded (bring-your-own)Paste a certificate + chain + private key. The key is encrypted at rest and only ever decrypted into the config the proxy pulls.
Let's Encryptsol2docker requests and auto-renews the certificate.

For Let's Encrypt, pick the challenge type:

  • HTTP-01 — needs the proxy reachable on :80 for the domains. Works across multiple Swarm replicas; issuance waits until every live replica is serving the challenge.
  • DNS-01 — works behind a firewall and for wildcards. Either add a DNS provider (currently Cloudflare) on the DNS providers tab for automatic TXT records — its credentials are encrypted at rest — or choose manual and add the TXT record yourself when prompted.

A staging Let's Encrypt option is available for testing without burning production rate limits. Until a managed certificate exists, :443 answers with a self-signed bootstrap certificate so the handshake never hangs.

Mutual TLS (client certificates)

Any L7 route can require client certificates: set mTLS to optional (verify if presented) or required, and provide the CA (PEM) that signs the allowed client certs. Requests without a valid client certificate are rejected with 403.

Per-route options

Each L7 route has Behavior and Headers sections. All of these hot-apply — no redeploy.

OptionEffect
Force HTTPSRedirects http://https://.
HSTSSends Strict-Transport-Security on HTTPS responses (with a configurable max-age).
WebSocketsAllow or reject Upgrade handshakes for this route.
Connect / read timeoutsPer-route upstream timeouts, in seconds.
Request headersExtra headers added to the upstream request.
Response headersExtra headers added to the response returned to the client.

Blocking & security

The proxy blocks abusive clients in-process (no NET_ADMIN, no fail2ban), and sol2docker persists the decisions so they fan out to every replica. Precedence is allow > deny > ban.

  • Allow / deny rules (the Allow/deny tab) — IPv4 or IPv6 CIDRs. An allow-listed client is never auto-banned; a denied CIDR is blocked immediately.
  • Bans (the Blocked tab) — manual bans, plus automatic bans the proxy reports when a client trips the thresholds. Bans are audited and raise a notification.
  • Rate limiting & auto-ban thresholds are configurable, with visible defaults, and update live without redeploying the proxy:
SettingDefaultMeaning
Auto-banonAutomatically block clients that exceed the thresholds below.
Max requests / window300 / 10sRequest-rate ceiling per client.
Max errors / window25 / 60s4xx/5xx ceiling per client.
Ban duration3600sHow long an auto-ban lasts.

On Swarm, run the proxy in host mode (the default deploy) so the real client IP is preserved — in routing-mesh mode the source IP is hidden and source-IP blocking can't work.

Managing it in the UI

The Reverse proxy page is organized into tabs, and every table is searchable and filterable:

  • Routes — your L7 routes.
  • Deployment — the proxy's stack → services → tasks (Swarm) or containers (standalone) in a hierarchy, with the normal service/task actions; click through to any detail page.
  • Certificates — uploaded and Let's Encrypt certs, with expiry and renew-now.
  • DNS providers — credentials for DNS-01.
  • Blocked — active bans.
  • Allow/deny — CIDR allow/deny rules.

How it works

sol2docker is the control plane; the proxy is a pull-based data plane — the same outbound-only, token-per-environment pattern as the node agent.

  • sol2docker computes one config bundle per environment (routes, certificates, L4 streams, ACME challenges, bans, allow/deny rules, security thresholds) and bumps a per-environment version on any change.
  • The proxy long-polls GET /api/v2/proxy/config; on a change it applies the new bundle via an atomic generation-pointer swap — new config is staged, then a single pointer flip makes it live, so a request never sees a torn state and no reload happens.
  • A malformed bundle is never applied — the proxy keeps serving the last good one and logs it.
  • Certificate private keys are decrypted only into the bundle served over the token-authenticated channel — never into an API response, log, or the audit trail.

Notes & limits

  • Allow/deny rules and bans match both IPv4 and IPv6 CIDRs and fan out to every replica. The proxy adds IPv6 :80/:443 listeners automatically when the host kernel has IPv6 (so a v6 client can reach it and be blocked cluster-wide); on a host with IPv6 disabled, only IPv4 listeners are created. v6 clients still only arrive if the Docker network the proxy runs on has IPv6 enabled.
  • L4 (TCP/UDP) streams are pass-through: the per-route L7 options (force-HTTPS, HSTS, headers, timeouts, per-route blocking) apply to L7 routes only.
  • The proxy needs to own :80/:443 on the node(s) it runs on — put other workloads behind it rather than next to it.