Both images had to be compiled where the stack runs - MinIO from source because upstream
publishes no image for the security release, Caddy through xcaddy for the caddy-l4 module.
On a small VM that failed: the Go builds run out of disk and memory, and an install that has
to compile two programs is a poor way to start a database.
They are now built once, for amd64 and arm64, and published to Docker Hub as
luciolelii/minio and luciolelii/caddy-l4. Each tag is the version its Dockerfile pins, read
from the Dockerfile by publish-images.sh so the two cannot drift: RELEASE.2025-10-15T17-29-55Z
for MinIO, and 2.11.4-l4-v0.1.2 (Caddy, then the module) for Caddy. docker-compose.yml names
those tags, overridable with MINIO_IMAGE and CADDY_IMAGE. The Dockerfiles stay, as the source
of the images.
The MinIO image still runs as 1000:1000, so what the README says about the data directory's
owner stands.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
PostgreSQL and MinIO kept their data in named Docker volumes, which sit under
/var/lib/docker on the system disk - the wrong place for the part of this stack
that only grows.
POSTGRES_DATA_PATH and MINIO_DATA_PATH, each optional and independent, point a
service at a host directory instead. Unset or empty keeps the volume, so an
existing installation behaves as before until it is moved on purpose. Compose
chooses bind or volume from the value itself, so one line per service does it.
The README carries what goes wrong otherwise: directories inside the disk and
not at its mount point (ext4's lost+found stops PostgreSQL from starting), the
owners the containers run as (70 and 1000), how to copy existing data without -v,
no network filesystems for PostgreSQL, and a disk that is not mounted at boot -
which makes Docker create the directory on the system disk and PostgreSQL start
empty, looking exactly like lost data.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The stack asked for four public names - one each for PostgreSQL, the S3 API, the
MinIO console and pgAdmin - and Caddy refuses to start if two of them are the
same, so a server with a single name could not run it.
DATA_DOMAIN replaces the four. The services share its one certificate, since a
certificate is for a name and not a port, and are told apart by port:
443 S3 API, path-style, at the root of the host
5432 PostgreSQL, TLS terminated by the Layer 4 listener
9443 MinIO console
5443 pgAdmin
The S3 API keeps the standard port because MinIO serves buckets from the root of
its host and other systems sign requests against it. The console's public port is
carried in MINIO_BROWSER_REDIRECT_URL, or MinIO sends the browser back to the S3
API's address. The console and pgAdmin host ports are configurable
(CONSOLE_PUBLIC_PORT, PGADMIN_PUBLIC_PORT).
An .env from the old layout fails at start with a message naming DATA_DOMAIN;
the README says how to move. Data volumes are untouched.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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>