3.8 KiB
PostgreSQL and MinIO public data stack
This Compose stack publishes four encrypted endpoints through Caddy:
- PostgreSQL on
postgres.example.com:5432using 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
Create public
A/AAAArecords for the four names and point them to the server.Allow inbound TCP ports
80,443, and5432in the host/cloud firewall. Restrict5432to known client address ranges whenever possible.Copy the environment template and replace every placeholder:
cp .env.example .env openssl rand -base64 36 docker compose config --quiet docker compose up -d --buildFollow 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.