Pull MinIO and Caddy instead of compiling them on the server

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>
This commit is contained in:
Lucio Lelii 2026-10-02 12:06:01 +02:00
parent 169ad32f0b
commit c576a44e6f
4 changed files with 87 additions and 15 deletions

View File

@ -33,6 +33,11 @@ PGADMIN_DEFAULT_PASSWORD=replace-with-a-third-independent-random-password
# Optional image override. Keep the same major version when upgrading an existing volume.
POSTGRES_IMAGE=postgres:17.11-alpine
# The other two images are pinned in docker-compose.yml to the versions their Dockerfiles build,
# and pulled from Docker Hub (luciolelii/minio, luciolelii/caddy-l4). Override only to test another:
# MINIO_IMAGE=luciolelii/minio:RELEASE.2025-10-15T17-29-55Z
# CADDY_IMAGE=luciolelii/caddy-l4:2.11.4-l4-v0.1.2
# Where the two big data sets live. Leave both unset to keep them in Docker volumes, under
# /var/lib/docker. To use a larger disk, give an absolute path to a directory ON it - a
# subdirectory, never the mount point itself, which on ext4 holds lost+found and makes PostgreSQL

View File

@ -21,12 +21,19 @@ point; traffic from Caddy to the services stays on an internal Docker network. C
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.
Two of the images are not stock ones, so they are built once, from `caddy.Dockerfile` and
`minio.Dockerfile`, and published to Docker Hub; the server only pulls them and compiles nothing.
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.
- `luciolelii/caddy-l4` is Caddy 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.
- `luciolelii/minio` is MinIO built from its latest upstream security release. Upstream did not
publish a container for that release, so the Dockerfile builds the pinned source tag.
Each image's tag is the version its Dockerfile pins (`RELEASE.2025-10-15T17-29-55Z`, and
`2.11.4-l4-v0.1.2` for Caddy version then module version), and `docker-compose.yml` names that tag.
Both are built for amd64 and arm64.
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.
@ -43,7 +50,8 @@ saved server definitions live in the `pgadmin-data` volume.
cp .env.example .env
openssl rand -base64 36
docker compose config --quiet
docker compose up -d --build
docker compose pull
docker compose up -d
```
4. Follow certificate issuance and startup:
@ -116,7 +124,7 @@ The two are independent: set one or both. Unset (or empty) means the named volum
sudo chown 1000:1000 /mnt/bigdisk/minio # MinIO, see minio.Dockerfile
```
2. **A new installation** needs nothing more: set the variables and `docker compose up -d --build`.
2. **A new installation** needs nothing more: set the variables and `docker compose up -d`.
3. **An installation that already holds data** must copy it first. Do not use `-v` anywhere:
@ -125,7 +133,7 @@ The two are independent: set one or both. Unset (or empty) means the named volum
docker run --rm -v humainflow-data-stack_postgres-data:/from -v /mnt/bigdisk/postgres:/to alpine cp -a /from/. /to/
docker run --rm -v humainflow-data-stack_minio-data:/from -v /mnt/bigdisk/minio:/to alpine cp -a /from/. /to/
# set the two variables in .env, then
docker compose up -d --build
docker compose up -d
```
The volume names carry the project name from `name:` in the compose file. Keep the old volumes
@ -145,5 +153,19 @@ Cautions:
An earlier version used `POSTGRES_DOMAIN`, `MINIO_API_DOMAIN`, `MINIO_CONSOLE_DOMAIN` and
`PGADMIN_DOMAIN`. Replace them in `.env` with a single `DATA_DOMAIN` (any one of the old names will
do, as long as it resolves to this server), open ports `9443` and `5443`, and run
`docker compose up -d --build`. Data volumes are untouched. Addresses change: the console moves from
`docker compose up -d`. Data volumes are untouched. Addresses change: the console moves from
its own name to `:9443`, pgAdmin to `:5443`, and PostgreSQL clients connect to `DATA_DOMAIN`.
## Publishing the MinIO and Caddy images
Only needed to change a version. Edit the version in the Dockerfile (`ARG MINIO_VERSION` in
`minio.Dockerfile`; the `caddy:` tag and the `caddy-l4@` version in `caddy.Dockerfile`), then, from a
machine with Docker and a Docker Hub login (`docker login -u luciolelii`, with an access token that can
write):
```sh
./publish-images.sh # both; or: ./publish-images.sh minio ./publish-images.sh caddy
```
and set the new tag in `docker-compose.yml` (or `MINIO_IMAGE` / `CADDY_IMAGE` in `.env`). If the Docker
Hub repositories are private, run `docker login` on the server before `docker compose pull`.

View File

@ -38,9 +38,9 @@ services:
logging: *default-logging
minio:
build:
context: .
dockerfile: minio.Dockerfile
# Pinned to the version minio.Dockerfile builds. Upstream publishes no image for this release, so
# the image is built and pushed by publish-images.sh; the server only pulls it.
image: ${MINIO_IMAGE:-luciolelii/minio:RELEASE.2025-10-15T17-29-55Z}
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER:?set MINIO_ROOT_USER in .env}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?set MINIO_ROOT_PASSWORD in .env}
@ -105,9 +105,9 @@ services:
logging: *default-logging
caddy:
build:
context: .
dockerfile: caddy.Dockerfile
# Caddy with the caddy-l4 module, built from caddy.Dockerfile by publish-images.sh. The tag is the
# Caddy version followed by the module's.
image: ${CADDY_IMAGE:-luciolelii/caddy-l4:2.11.4-l4-v0.1.2}
environment:
TLS_CONTACT: ${TLS_CONTACT:?set TLS_CONTACT in .env}
DATA_DOMAIN: ${DATA_DOMAIN:?set DATA_DOMAIN in .env}

45
publish-images.sh Executable file
View File

@ -0,0 +1,45 @@
#!/bin/sh
# Builds the two images this stack would otherwise compile on the server - MinIO and Caddy with the
# caddy-l4 module - for amd64 and arm64 and pushes them to Docker Hub.
# Usage: ./publish-images.sh [minio] [caddy] (default: both)
#
# The tag is the version the Dockerfile pins, read from it, so the two cannot drift apart:
# luciolelii/minio:RELEASE.2025-10-15T17-29-55Z
# luciolelii/caddy-l4:2.11.4-l4-v0.1.2 (Caddy version, then the caddy-l4 version)
# "latest" is pushed too, but docker-compose.yml names the version tag. Changing a version in a
# Dockerfile and re-running this is how an upgrade is published.
set -eu
cd "$(dirname "$0")"
REGISTRY="${REGISTRY:-luciolelii}"
PLATFORMS="${PLATFORMS:-linux/amd64,linux/arm64}"
BUILDER="${BUILDER:-mcp-publisher}"
# A multi-platform build needs a builder that can run both architectures; Docker's default one
# cannot push a multi-platform image.
docker buildx inspect "$BUILDER" >/dev/null 2>&1 \
|| docker buildx create --name "$BUILDER" --driver docker-container --bootstrap
publish() {
name="$1"; dockerfile="$2"; tag="$3"
echo "==> $REGISTRY/$name:$tag ($PLATFORMS)"
docker buildx build --builder "$BUILDER" --platform "$PLATFORMS" -f "$dockerfile" \
-t "$REGISTRY/$name:$tag" -t "$REGISTRY/$name:latest" --push .
}
minio_tag() { sed -n 's/^ARG MINIO_VERSION=//p' minio.Dockerfile; }
caddy_tag() {
caddy="$(sed -n 's/^FROM caddy:\(.*\)-builder-alpine.*/\1/p' caddy.Dockerfile)"
l4="$(sed -n 's/.*caddy-l4@\(v[0-9.]*\).*/\1/p' caddy.Dockerfile)"
echo "$caddy-l4-$l4"
}
targets="${*:-minio caddy}"
for target in $targets; do
case "$target" in
minio) publish minio minio.Dockerfile "$(minio_tag)" ;;
caddy) publish caddy-l4 caddy.Dockerfile "$(caddy_tag)" ;;
*) echo "Unknown image: $target (expected minio or caddy)" >&2; exit 1 ;;
esac
done