diff --git a/.env.example b/.env.example index 6a46de1..281e2c2 100644 --- a/.env.example +++ b/.env.example @@ -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 diff --git a/README.md b/README.md index c0b4363..c91063b 100644 --- a/README.md +++ b/README.md @@ -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`. diff --git a/docker-compose.yml b/docker-compose.yml index e7b339a..2853aec 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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} diff --git a/publish-images.sh b/publish-images.sh new file mode 100755 index 0000000..f292360 --- /dev/null +++ b/publish-images.sh @@ -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