Fleetdock

External database access

Expose managed databases to applications outside your private network through the Fleetdock Gateway — HAProxy TCP proxy, CIDR allowlists, and application credentials.

Fleetdock can expose managed databases to applications outside the private network through a Fleetdock Gateway (HAProxy). The control plane generates configuration from persisted endpoint state; HAProxy proxies TCP traffic with one public port per database.

The API never proxies database connections. Desired state lives in database_endpoints; HAProxy config is derived and reconciled by the control-plane worker.

Architecture

External app → gateway.example.com:15432 (TCP) → HAProxy → server.address:instance.port → Database

The gateway is colocated with the Manager in the Docker Compose stack. The control plane writes haproxy.cfg and performs a hitless reload through the HAProxy master socket. A config HAProxy rejects is rolled back automatically — the running workers keep serving the previous version. Reloads only happen when the generated config actually changes.

The gateway container must have a network route to server.address:instance.port. If it cannot reach the database, the endpoint reports error with the unreachable address.

Enable the gateway

External access is off by default — turning it on publishes a 50-port range on the host, which most installs never need.

fleetdock gateway enable

That flips FLEETDOCK_GATEWAY_ENABLED, activates the gateway compose profile (HAProxy and its one-shot init, sharing /var/lib/fleetdock/gateway with the control plane), and prints the port range to open in your firewall. fleetdock gateway disable reverses it.

By hand, the equivalent is FLEETDOCK_GATEWAY_ENABLED=true in .env plus docker compose --profile gateway up -d.

Hostname

FLEETDOCK_GATEWAY_PUBLIC_HOST defaults to your main FLEETDOCK_DOMAIN. The gateway speaks raw TCP on its own ports — no vhost, no SNI, no TLS termination — so it needs no DNS record of its own.

Set it separately when the main record cannot carry raw TCP. The usual culprit is a CDN-proxied record — Cloudflare's orange cloud forwards 80/443 only, so every database endpoint on that name is unreachable. Point a grey-clouded gw.example.com at the host and set the variable to it.

The hostname is copied into each endpoint when public access is enabled, so changing it later does not rewrite connection URLs already issued. fleetdock domain explains how to adopt a new one.

Port range and options

FLEETDOCK_GATEWAY_PORT_RANGE_START=15432
FLEETDOCK_GATEWAY_PORT_RANGE_END=15481

The published port range and the allocator range come from the same variables. Widen one and you must widen the other, or endpoints are assigned ports that nothing forwards — a failure only external clients can see.

Optional:

FLEETDOCK_GATEWAY_DIAG_PORT=15431          # source-IP diagnostic; 0 disables it
FLEETDOCK_GATEWAY_SOURCE_IP_MODE=direct    # or proxy-protocol, behind an L4 load balancer
FLEETDOCK_GATEWAY_CONFIG_PATH=/var/lib/fleetdock/gateway/haproxy.cfg
FLEETDOCK_GATEWAY_MASTER_SOCKET=/var/lib/fleetdock/gateway/haproxy-master.sock
FLEETDOCK_GATEWAY_ADMIN_SOCKET=/var/lib/fleetdock/gateway/haproxy-admin.sock

Enable public access per database

On the database page → ConnectivityEnable public access with allowed CIDRs. Or use the API:

  • POST /v1/databases/{id}/public-access — enable with CIDR allowlist
  • PATCH /v1/databases/{id}/public-access — update CIDRs
  • DELETE /v1/databases/{id}/public-access — disable

Check status with GET /v1/databases/{id}/connectivity.

Private vs public endpoints

PathWhen to useEndpoint
PrivateApps on the same private network as the database serverserver.address:instance.port (internal)
PublicApps outside the private networkFLEETDOCK_GATEWAY_PUBLIC_HOST:assigned-port (gateway)

Public access is disabled by default. When enabled, the gateway hostname and port stay stable across migrations — only the private backend target changes. See Migration.

Application credentials

Create scoped credentials under Application credentials on the database page (or via POST /v1/databases/{id}/credentials). Passwords are encrypted at rest; the connection URL is shown once on create or rotate.

Permission profiles:

ProfileScope
readonlySELECT on the database
readwriteSELECT, INSERT, UPDATE, DELETE
adminFull database-scoped privileges

Instance root credentials are never exposed for public use. Create application credentials with least-privilege profiles for external applications.

Rotate or revoke credentials via:

  • POST /v1/databases/{id}/credentials/{credentialId}/rotate
  • DELETE /v1/databases/{id}/credentials/{credentialId}

Source IP fidelity

Allowlists match the source address HAProxy observes, which is not always your client's address. Getting this wrong rejects every connection, and the rejection looks like the server closing the connection immediately.

DeploymentAddress HAProxy seesAllowlists work?
Linux host, published ports (iptables DNAT)the real client addressYes
Docker Desktop (macOS/Windows)the Docker VM's NAT addressNo — every real client is rejected
Behind an L4 load balancerthe balancer's addressOnly with FLEETDOCK_GATEWAY_SOURCE_IP_MODE=proxy-protocol
network_mode: host on Linuxthe real client addressYes, and avoids publishing a large port range

Find out what your gateway actually sees. Run this from the machine that will connect:

curl http://gateway.example.com:15431

It prints the address to allowlist.

A bare address is accepted and treated as a single host (/32 or /128). A CIDR with host bits set — 10.1.2.3/24 — is rejected rather than silently widened to a network you did not ask for.

Changing the allowlist

Edit it in place from the connectivity panel, or with PATCH /v1/databases/{id}/public-access. The assigned port is preserved, so existing clients keep working. Disabling and re-enabling allocates a different port and breaks every connection URL already handed out.

If the panel reports rejected connections with no successful sessions, the allowlist does not match where clients are arriving from — check the diagnostic port above.

What Fleetdock can and cannot verify

FactHow it is determined
The route is programmed and the gateway can reach the databaseHAProxy's own health check, read over the stats socket
The database accepts TLSThe control plane dials the database directly and runs a real protocol probe
The published port is reachable from the internetNot determinable — firewalls, NAT, and DNS sit outside the control plane

Endpoint status reflects those facts:

StatusMeaning
pendingNot yet in the applied gateway config, or the last apply failed — see last_error
activeIn the applied config, and HAProxy reports the database reachable
errorIn the applied config, but the database is down. The listener stays up so a restart does not reallocate the port.
disabling / disabledBeing removed / removed

TLS

HAProxy uses TCP passthrough — it does not terminate TLS at the gateway. Clients negotiate TLS with the database directly, so the database must support it for sslmode=require to work.

tls_status is measured against the database itself: a Postgres SSLRequest probe, or the CLIENT_SSL capability bit in the MySQL/MariaDB handshake.

When tls_status is unsupported while tls_mode is required, issued connection URLs carry sslmode=require and will fail until TLS is enabled on the database. Fleetdock does not quietly downgrade the requirement, because that would drop encryption you asked for.

Security

  • Public access is disabled by default
  • CIDR allowlists are enforced in HAProxy (tcp-request connection reject unless …)
  • Explicit 0.0.0.0/0 is required for allow-from-anywhere — the UI warns when you choose it
  • Instance root credentials are never exposed for public use
  • Connectivity and credential changes are written to an append-only audit_events table (not yet exposed through the API or dashboard — query it with fleetdock psql)

See Security for the full trust model.

Production

Open the gateway port range in the host firewall. The control plane cannot verify reachability from outside — firewalls, NAT and DNS sit beyond it — so this is the step most often missed. The dashboard and API stay on 443; gateway ports are separate TCP listeners on the same host.

On Linux, network_mode: host for the gateway service both avoids publishing a large port range and preserves real client addresses for allowlists.

Troubleshooting

SymptomCheck
Endpoint stuck pendinglast_error on the endpoint; the reconcile_gateway operation; whether the gateway container is healthy
Endpoint shows errorlast_error names the unreachable host:port — can the gateway container reach it?
Connection closes immediatelyThe allowlist. Run curl http://<public-host>:15431 from the connecting machine and compare. The panel also reports rejected-connection counts.
server does not support SSL, but SSL was requiredThe database does not accept TLS — enable it, or recreate the endpoint with tls_mode=preferred
Cannot connect externallyDNS to FLEETDOCK_GATEWAY_PUBLIC_HOST, host firewall on the port range, and that the range is actually published
Connection refusedGateway service running; port assigned within PORT_RANGE_STARTEND

See also Troubleshooting — Gateway.

MVP limitations

  • No TLS termination at gateway (passthrough only)
  • One public port per database (no hostname/SNI routing)
  • The gateway must have a network route to server.address:port (same as DB admin today)
  • CIDR allowlists depend on the gateway observing real client addresses — see Source IP fidelity above

API reference

Gateway and credential endpoints are documented under the Databases tag in the API reference.

On this page