The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Run Caddy, your app, and PostgreSQL in one Docker Compose project, but publish only Caddy’s web ports to the VPS. Caddy forwards requests to the app over the Compose network; the app connects to PostgreSQL at db:5432 on that same private container network. With no PostgreSQL ports: mapping and no firewall rule for port 5432, the database has no public host port.
How the traffic flows
The boundary is the important part: internet traffic reaches Caddy on TCP ports 80 and 443 (and optionally UDP 443 for HTTP/3). Caddy proxies requests to the app using its Compose service name and the port the app listens on inside its container. The app connects to PostgreSQL by service name on port 5432. Neither app-to-database nor proxy-to-app traffic needs a host port mapping.
As an Amazon Associate I earn from qualifying purchases.
- Public: browser or client → VPS → Caddy.
- Private Compose network: Caddy →
app:3000; app →db:5432. - Not published: PostgreSQL has no host
ports:mapping, so external clients cannot connect through a published database port.
Within a Compose project, services can discover each other by service name. localhost inside a container refers to that container itself, not another service. Caddy’s Compose guidance explains that containers on the same Docker network can communicate without publishing ports: Caddy Docker Compose guidance.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Example Docker Compose and Caddy configuration
This is an illustrative starting point, not a drop-in production stack. Replace the image names, tags, ports, credentials, storage paths, health checks, and secret handling for your app and the exact image versions you choose.
#1 Best Overall
services:
caddy:
image: caddy:<pinned-version>
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- app
app:
image: <your-app-image>
restart: unless-stopped
environment:
DATABASE_URL: <secret-backed-connection-string-to-db>
expose:
- "3000"
depends_on:
- db
db:
image: postgres:<pinned-version>
restart: unless-stopped
environment:
POSTGRES_PASSWORD: <secret>
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
caddy_data:
caddy_config:
postgres_data:
The critical omission is deliberate: db has no ports: section. The app’s illustrative expose entry is not a host publication; Compose peers can communicate without it. The storage path shown for PostgreSQL is not universal: check the documentation for the precise image tag and setup. Docker’s PostgreSQL guide, for example, uses postgres:18 with /var/lib/postgresql; do not assume that path matches every release or configuration. See Docker’s PostgreSQL guide.
Use a dedicated, least-privilege database user for the application instead of the PostgreSQL superuser. Keep credentials out of source control and choose a secret-injection approach suitable for your server. Pin image versions for reproducible deployments and establish how you will review and apply updates; a floating latest tag is not a production pin.
Rank #2
Save this as Caddyfile, changing the hostname and internal app port to match your deployment:
example.com {
reverse_proxy app:3000
}
example.com is a placeholder. The upstream target must use the Compose service name and the port on which the app listens inside its container—not a host port. Caddy’s reverse_proxy directive reference documents the proxy directive. The official Caddy image’s default Caddyfile listens on port 80; it does not by itself configure a hostname for automatic TLS. See the official Caddy image description.
Rank #3
- HP MicroServer Gen10 Plus Tower Server for Business with Microsoft Windows Server 2019 OS!
- Intel Xeon E-2224 Quad-Core 3.4GHz 8MB CPU, Up To 4.6GHz Turbo
- 32GB (2 x 16GB) DDR4 PC4-21300 2666MHz Unbuffered Memory
- 16TB (4 x 4TB) 7.2K 6Gb/s SATA 3.5" HDDs in RAID
- Hard drives and memory upgrades included separately NOT installed, installation required.
Prepare DNS, networking, and HTTPS
- Point your domain at the VPS. Create an A record for the server’s IPv4 address and an AAAA record only if you intend to serve IPv6 and have it configured correctly.
- Allow web traffic to reach Caddy. Permit inbound TCP 80 and 443 in both the VPS firewall and any provider-level network firewall, and ensure those ports reach the Caddy container. If enabling HTTP/3, also allow UDP 443.
- Keep the services on the same Compose network. A basic Compose project creates a project network for its services. Use
appanddbas their hostnames from peer containers. - Configure the two internal connections. Set the app’s database host to
dband port to5432; set Caddy’s upstream toapp:<container-port>, using the app’s actual listening port. - Persist Caddy’s state. Keep its writable
/dataand/configdirectories on persistent storage. Caddy stores important TLS-related data under/data. Its automatic HTTPS provisions and renews certificates when the hostname, DNS, and public reachability requirements are met; see Caddy’s Automatic HTTPS documentation and Caddy’s HTTPS quick start.
Persist PostgreSQL data separately from the replaceable container as well. Docker volumes are persistent storage, not backups: decide how to back up the database, protect the backup, and periodically verify that it can be restored. The right backup schedule depends on how much data you can afford to lose and how quickly you need recovery.
Start the stack and verify the boundary
- From the directory containing the Compose file, start the services:
docker compose up -d. - Inspect the service logs with
docker compose logs -f caddy app db. Resolve configuration, startup, and connection errors before treating the deployment as ready. - Open the domain and confirm the app responds over HTTPS. Check Caddy’s logs for successful certificate provisioning and verify the certificate in a browser or with your normal TLS inspection tool.
- Inspect the published ports with
docker compose psand the host’s listening sockets or firewall configuration. Confirm PostgreSQL is not bound to a public host interface and that neither the VPS firewall nor provider firewall permits public inbound 5432. - Document how to update the pinned images and how to back up and restore both application data and database data.
depends_on controls startup ordering in Compose; it does not prove that PostgreSQL is ready to accept connections when the app starts. Configure the app to retry failed database connections or implement an appropriate readiness check.
Rank #4
What if you need database access from your laptop?
Remote administration is a separate access design from the app’s private service connection. Prefer a VPN or an authenticated SSH tunnel rather than making PostgreSQL publicly reachable. If you deliberately need a host mapping for local tooling, bind it to loopback, for example 127.0.0.1:5432:5432, then connect through an SSH tunnel or another secured route. A loopback binding is not publicly reachable through that mapping, but firewall and access policy remain separate layers.
A mapping such as 5432:5432 can bind on all host interfaces. Docker warns that exposing PostgreSQL on 0.0.0.0:5432 makes it accessible to devices that can reach the host. Do not add that mapping on a public VPS for convenience; if direct public access is an intentional requirement, it needs a separately designed and enforced access boundary. See Docker’s PostgreSQL external-access guidance.
Quick Recap
Best Value
Common deployment failures
- Caddy returns a gateway error: confirm the app is running, listening on the configured container port, and reachable on the shared Compose network. Use the service name, not
localhost. - HTTPS does not provision: check the site hostname in the Caddyfile, public DNS records, inbound TCP 80/443, and whether Caddy’s persistent writable
/datastorage is available. - The app cannot connect to PostgreSQL: check the database service name, port 5432, credentials, database initialization, and app retry/readiness behavior. A published host port is not needed for this connection.
- PostgreSQL appears exposed: remove an unintended host
ports:mapping, then check host and provider firewall rules. Docker networking and firewall policy are distinct controls.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




