Go to file
Lucio Lelii 09d1871a4b PostgreSQL and MinIO for the platform, behind one TLS entry point
A Compose stack that publishes PostgreSQL, the MinIO S3 API and console, and
pgAdmin through Caddy, which is the only container with public ports. The data
services sit on an internal network.

Caddy is built with the pinned caddy-l4 module so it can terminate TLS on the
PostgreSQL wire protocol itself (SSLRequest), not only HTTP. MinIO is built from
its pinned upstream security release, which has no published image. Every
secret comes from .env, which is ignored; .env.example documents it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:32:05 +02:00
.dockerignore PostgreSQL and MinIO for the platform, behind one TLS entry point 2026-10-02 09:32:05 +02:00
.env.example PostgreSQL and MinIO for the platform, behind one TLS entry point 2026-10-02 09:32:05 +02:00
.gitignore PostgreSQL and MinIO for the platform, behind one TLS entry point 2026-10-02 09:32:05 +02:00
Caddyfile PostgreSQL and MinIO for the platform, behind one TLS entry point 2026-10-02 09:32:05 +02:00
README.md PostgreSQL and MinIO for the platform, behind one TLS entry point 2026-10-02 09:32:05 +02:00
caddy.Dockerfile PostgreSQL and MinIO for the platform, behind one TLS entry point 2026-10-02 09:32:05 +02:00
docker-compose.yml PostgreSQL and MinIO for the platform, behind one TLS entry point 2026-10-02 09:32:05 +02:00
minio.Dockerfile PostgreSQL and MinIO for the platform, behind one TLS entry point 2026-10-02 09:32:05 +02:00

README.md

PostgreSQL and MinIO public data stack

This Compose stack publishes four encrypted endpoints through Caddy:

  • PostgreSQL on postgres.example.com:5432 using the native PostgreSQL TLS negotiation;
  • the MinIO S3 API on https://s3.example.com;
  • the MinIO console on https://minio.example.com;
  • the multi-user pgAdmin interface on https://pgadmin.example.com.

PostgreSQL and MinIO have no directly published container ports. Caddy is the only public entry point; traffic from Caddy to the services stays on an internal Docker network. Caddy persists its ACME account, certificates and private keys in caddy-data, so restarts do not trigger unnecessary certificate reissuance.

The Caddy image is built with the pinned caddy-l4 module because stock Caddy only proxies HTTP. The module understands PostgreSQL’s initial SSLRequest, terminates TLS with Caddy’s automatically managed certificate, and sends the decrypted connection to PostgreSQL only on the private network.

The MinIO server is built from its latest upstream security release. Upstream did not publish a container for that release, so the included multi-stage Dockerfile builds the pinned source tag.

pgAdmin is pinned to version 9.18 and runs in server mode. Its users, sessions, preferences and saved server definitions live in the pgadmin-data volume.

Start

  1. Create public A/AAAA records for the four names and point them to the server.

  2. Allow inbound TCP ports 80, 443, and 5432 in the host/cloud firewall. Restrict 5432 to known client address ranges whenever possible.

  3. Copy the environment template and replace every placeholder:

    cp .env.example .env
    openssl rand -base64 36
    docker compose config --quiet
    docker compose up -d --build
  4. Follow certificate issuance and startup:

    docker compose logs -f caddy postgres minio pgadmin

Ports 80 and 443 must reach Caddy from the public Internet for the usual ACME HTTP/TLS challenges. The names must be eligible for Let’s Encrypt issuance; a restrictive DNS CAA record can refuse it.

Connect

PostgreSQL requires TLS at the public listener. verify-full both encrypts the connection and checks that the certificate matches the hostname:

psql "host=postgres.example.com port=5432 dbname=humainflow user=humainflow sslmode=verify-full"

MinIO/S3 clients use https://s3.example.com; administrators open https://minio.example.com. MINIO_SERVER_URL is set to the public S3 hostname so presigned URLs remain valid behind the reverse proxy.

Open https://pgadmin.example.com and sign in with PGADMIN_DEFAULT_EMAIL and PGADMIN_DEFAULT_PASSWORD. On the first login, register the database with these values:

  • host: postgres (the Compose service name, not the public hostname);
  • port: 5432;
  • maintenance database: the value of POSTGRES_DB;
  • username/password: a PostgreSQL role and its password.

The initial pgAdmin administrator can create additional pgAdmin accounts from User Management. pgAdmin accounts only control access to the web interface: create separate least-privilege PostgreSQL roles for database authorization. Each person should use their own database role rather than sharing POSTGRES_USER. Changing the default pgAdmin password in .env after the first start does not update the account already stored in pgadmin-data; change it from pgAdmin instead.

Operations

The persistent volumes are postgres-data, minio-data, pgadmin-data, and caddy-data. Back up the first three; do not treat Docker volumes as backups. Never run docker compose down -v unless permanent deletion of both data stores, pgAdmin’s configuration and Caddy’s certificate state is intended.

Upgrade PostgreSQL one major version at a time using the PostgreSQL upgrade procedure. Updating an image tag alone does not migrate an existing database volume.