Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions .github/workflows/demo-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
name: demo build

# Builds the -demo variants of the 4.3 - 5.2 images under demo/: the official
# image for each series, plus a database with a Plone site already created.
#
# The base images are not rebuilt. They are the official `plone` images,
# pinned by digest in demo/Makefile; see mirror-official.yml for why these
# series cannot be rebuilt faithfully.

on:
push:
branches: [main]
paths:
- 'demo/**'
- 'legacy/shared/create-plone-site.sh'
- 'legacy/shared/smoke-test.sh'
- 'legacy/shared/json-probe.py'
- '.github/workflows/demo-build.yml'
pull_request:
paths:
- 'demo/**'
- 'legacy/shared/create-plone-site.sh'
- 'legacy/shared/smoke-test.sh'
- 'legacy/shared/json-probe.py'
- '.github/workflows/demo-build.yml'
workflow_dispatch:

permissions:
contents: read
packages: write

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: make lint
working-directory: demo
run: make lint

build:
needs:
- lint
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Keep in sync with SERIES in demo/Makefile.
version: ["4.3", "5.0", "5.1", "5.2"]
env:
# Pinned on purpose, as in legacy-build.yml (see #209).
GHCR_IMAGE: ghcr.io/plone/plone.docker
DOCKERHUB_IMAGE: plone/plone
# Secrets cannot be referenced from an `if:` condition; see
# legacy-build.yml for the same pattern.
HAS_DOCKERHUB: ${{ secrets.DOCKERHUB_USERNAME != '' && secrets.DOCKERHUB_TOKEN != '' }}
steps:
- uses: actions/checkout@v7

- name: Resolve full version from the Makefile
id: ver
run: |
FULL=$(sed -n "s/^FULL_VERSION_${{ matrix.version }} := //p" demo/Makefile)
test -n "${FULL}"
echo "full=${FULL}" >> "${GITHUB_OUTPUT}"

# Starts the official image, creates the site through
# legacy/shared/create-plone-site.sh, stops Zope cleanly and builds
# demo/Dockerfile on top. No buildx builder is set up on purpose: the
# plain `docker build` uses the daemon's own, which is all this needs.
- name: Build -demo image
working-directory: demo
run: make demo-${{ matrix.version }}

# SMOKE_EXPECT_SITE=1 asserts the site is ALREADY there on first start,
# with no creation step - the only check that distinguishes a correctly
# seeded demo image from its own base image.
- name: Smoke test -demo
working-directory: demo
run: make test-demo-${{ matrix.version }}

- name: Login to GHCR
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Login to Docker Hub
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' && env.HAS_DOCKERHUB == 'true'
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

# Two tags per series: the full version and the series, each -demo.
- name: Push
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
run: |
for tag in "${{ steps.ver.outputs.full }}-demo" "${{ matrix.version }}-demo"; do
docker tag "plone-demo:${tag}" "${GHCR_IMAGE}:${tag}"
docker push "${GHCR_IMAGE}:${tag}"
if [ "${HAS_DOCKERHUB}" = "true" ]; then
docker tag "plone-demo:${tag}" "${DOCKERHUB_IMAGE}:${tag}"
docker push "${DOCKERHUB_IMAGE}:${tag}"
fi
done
if [ "${HAS_DOCKERHUB}" != "true" ]; then
echo "::notice::DOCKERHUB_USERNAME/DOCKERHUB_TOKEN not set - pushed to GHCR only."
fi
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
# Changelog
- Add `-demo` variants of Plone 4.3, 5.0, 5.1 and 5.2 (`4.3-demo`, `5.0-demo`, `5.1-demo`, `5.2-demo` and their full-version tags), each the official image with a Plone site already created at `/Plone`. Refs #211
[@ericof]
- Fix the smoke test's JSON gate passing when the probe script failed to compile, and make the probe run on Python 3. Refs #211
[@ericof]
- Publish Plone 5.2.14 as `plone/plone:5.2.14` (and GHCR), copied by digest from the official `plone:5.2.14`. `plone/plone:latest`, `5`, `5.2` and `python38` now point at 5.2.14 instead of 5.2.4. Refs #210
[@ericof]
- Add Plone 1.0 image to the legacy matrix, completing it. Refs #197
Expand Down
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ listed; earlier point releases remain in the repository and on Docker Hub.

| Plone | Python | Variant | Built from | Published tags |
|---|---|---|---|---|
| 5.2.14 | 3.8 | Debian | [`5.2/5.2.14/debian/Dockerfile`](5.2/5.2.14/debian/Dockerfile) | `latest`, `5`, `5.2`, `5.2.14`, `python38`, `5-python38`, `5.2-python38`, `5.2.14-python38` |
| 5.2.14 | 3.8 | Debian | [`5.2/5.2.14/debian/Dockerfile`](5.2/5.2.14/debian/Dockerfile) | `latest`, `5`, `5.2`, `5.2.14`, `python38`, `5-python38`, `5.2-python38`, `5.2.14-python38`, `5.2-demo`, `5.2.14-demo` |
| 5.2.14 | 3.8 | Alpine | [`5.2/5.2.14/alpine/Dockerfile`](5.2/5.2.14/alpine/Dockerfile) | *not published* |
| 5.2.14 | 3.7 | Debian | [`5.2/5.2.14/python37/Dockerfile`](5.2/5.2.14/python37/Dockerfile) | *not published* |
| 5.2.14 | 2.7 | Debian | [`5.2/5.2.14/python2/Dockerfile`](5.2/5.2.14/python2/Dockerfile) | *not published* |
| 5.1.6 | 2.7 | Debian | [`5.1/5.1.6/debian/Dockerfile`](5.1/5.1.6/debian/Dockerfile) | `5.1`, `5.1.6` |
| 5.1.6 | 2.7 | Debian | [`5.1/5.1.6/debian/Dockerfile`](5.1/5.1.6/debian/Dockerfile) | `5.1`, `5.1.6`, `5.1-demo`, `5.1.6-demo` |
| 5.1.6 | 2.7 | Alpine | [`5.1/5.1.6/alpine/Dockerfile`](5.1/5.1.6/alpine/Dockerfile) | `5.1-alpine`, `5.1.6-alpine` |
| 5.0.8 | 2.7 | Debian | [`5.0/5.0.8/debian/Dockerfile`](5.0/5.0.8/debian/Dockerfile) | `5.0`, `5.0.8` |
| 5.0.8 | 2.7 | Debian | [`5.0/5.0.8/debian/Dockerfile`](5.0/5.0.8/debian/Dockerfile) | `5.0`, `5.0.8`, `5.0-demo`, `5.0.8-demo` |
| 5.0.8 | 2.7 | Alpine | [`5.0/5.0.8/alpine/Dockerfile`](5.0/5.0.8/alpine/Dockerfile) | *not published* |
| 4.3.19 | 2.7 | Debian | [`4.3/4.3.19/debian/Dockerfile`](4.3/4.3.19/debian/Dockerfile) | `4`, `4.3`, `4.3.19` |
| 4.3.19 | 2.7 | Debian | [`4.3/4.3.19/debian/Dockerfile`](4.3/4.3.19/debian/Dockerfile) | `4`, `4.3`, `4.3.19`, `4.3-demo`, `4.3.19-demo` |
| 4.3.19 | 2.7 | Alpine | [`4.3/4.3.19/alpine/Dockerfile`](4.3/4.3.19/alpine/Dockerfile) | `4-alpine`, `4.3-alpine`, `4.3.19-alpine` |
| 4.2.6 | 2.7.18 | Debian *(legacy)* | [`legacy/4.2/Dockerfile`](legacy/4.2/Dockerfile) | `4.2`, `4.2.6`, `4.2-demo`, `4.2.6-demo` |
| 4.1.6 | 2.6.9 | Debian *(legacy)* | [`legacy/4.1/Dockerfile`](legacy/4.1/Dockerfile) | `4.1`, `4.1.6`, `4.1-demo`, `4.1.6-demo` |
Expand All @@ -65,6 +65,7 @@ alone do not say which:
|---|---|---|
| 4.3 - 5.2 | the official `plone` image | `docker pull plone:5.2.14` |
| 5.2.14 (Debian) | also `plone/plone` and GHCR | `docker pull plone/plone:5.2.14`<br>`docker pull ghcr.io/plone/plone.docker:5.2.14` |
| 4.3 - 5.2 `-demo` | `plone/plone` and GHCR only | `docker pull plone/plone:5.2-demo`<br>`docker pull ghcr.io/plone/plone.docker:5.2-demo` |
| `legacy/` | `plone/plone` and GHCR | `docker pull plone/plone:4.2`<br>`docker pull ghcr.io/plone/plone.docker:4.2` |

The 5.2.14 tags on `plone/plone` and GHCR are copies of the official image, made
Expand All @@ -79,7 +80,9 @@ them.
prefer `plone:<tag>`.

Each legacy series publishes four tags — the full version and the series, each
with a `-demo` counterpart that ships a Plone site already created.
with a `-demo` counterpart that ships a Plone site already created. The 4.3 -
5.2 series have the same `-demo` counterparts, built on the official images;
see [`demo/`](demo/).

The 4.3 - 5.2 images take Python from an upstream `python:*` base image. The
`legacy/` images build the interpreter from source, because no usable base
Expand Down
2 changes: 2 additions & 0 deletions demo/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Seeded databases, written by seed.sh and baked into the images.
build/
31 changes: 31 additions & 0 deletions demo/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# syntax=docker/dockerfile:1
#
# The -demo variant of the 4.3 - 5.2 images.
#
# One Dockerfile serves all four series: it takes an official base image and a
# database seeded from it (see seed.sh), and adds nothing else — same Zope,
# same Python, same add-ons, same entrypoint behind a small wrapper.
#
# ./seed.sh plone:5.2.14 build/5.2
# docker build --build-arg BASE_IMAGE=plone:5.2.14 --build-arg SEED=build/5.2 \
# -t plone-demo:5.2.14-demo .
#
# or just `make demo-5.2`.
#
# NOT FOR PRODUCTION: the site's admin account is admin / admin, inherited
# from the base image's buildout.
ARG BASE_IMAGE
FROM ${BASE_IMAGE}

ARG BASE_IMAGE
ARG SEED=build/seed

LABEL org.opencontainers.image.description="Demo variant of ${BASE_IMAGE}: identical, but ships a database with a Plone site already created at /Plone. Migration, demonstration and archaeology only — NOT for production."

# The only differences from the base image: the seeded database, and the
# wrapper that copies it into /data on first start.
COPY --chown=plone:plone ${SEED}/ /app/seed/
COPY --chmod=755 demo-entrypoint.sh /demo-entrypoint.sh

ENTRYPOINT ["/demo-entrypoint.sh"]
CMD ["start"]
112 changes: 112 additions & 0 deletions demo/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# -demo variants of the 4.3 - 5.2 images.
# All tooling runs via docker: no local installs required beyond curl.

IMAGE_PREFIX ?= plone-demo
DOCKER ?= docker

# The official images are linux/amd64 only. On an arm64 host (Apple Silicon)
# they run under emulation.
PLATFORM ?= linux/amd64

# Every series with a -demo variant, oldest first. Keep in sync with the
# matrix in ../.github/workflows/demo-build.yml.
SERIES := 4.3 5.0 5.1 5.2

# series -> full version, and the official image it is built on, pinned by
# digest so that a demo image is always seeded from exactly the image that was
# reviewed. Resolve a digest with:
# docker buildx imagetools inspect --format '{{.Manifest.Digest}}' plone:<tag>
FULL_VERSION_4.3 := 4.3.19
FULL_VERSION_5.0 := 5.0.8
FULL_VERSION_5.1 := 5.1.6
FULL_VERSION_5.2 := 5.2.14
BASE_4.3 := docker.io/library/plone:4.3.19@sha256:a717118d21b880e96f83a96fce868ce15bd96b102225b1691f61fa1157d26840
BASE_5.0 := docker.io/library/plone:5.0.8@sha256:010d8e92106ef5aa3ad43664780f29c8844b79a4b89bb203b051f9a690c38274
BASE_5.1 := docker.io/library/plone:5.1.6@sha256:067ec2433da954c1fb1f9abad7233b7a7409688a913ff048871a0644d24827a7
BASE_5.2 := docker.io/library/plone:5.2.14@sha256:e73ae6e9864e43d4d49169f9fda09012d48c7720e8e204a8b0304b764600923b

# [V 2026-10-08] The signal that stops each base image cleanly. 4.3 - 5.1 trap
# TERM and run `bin/instance stop`. 5.2 execs `bin/instance console`, which
# ignored TERM until docker's 120s timeout killed it, and stops in about a
# second on INT.
STOP_SIGNAL_4.3 := TERM
STOP_SIGNAL_5.0 := TERM
STOP_SIGNAL_5.1 := TERM
STOP_SIGNAL_5.2 := INT

# [V 2026-10-08] Where each base image logs, for the smoke test's product-load
# check: 4.3 - 5.1 write /data/log/instance.log; 5.2 logs to stdout/stderr
# and writes no log file at all.
LOG_SOURCE_4.3 := file
LOG_SOURCE_5.0 := file
LOG_SOURCE_5.1 := file
LOG_SOURCE_5.2 := docker

define require_version
@test -n "$(FULL_VERSION_$(1))" || { \
echo "No FULL_VERSION_$(1) in the Makefile — is '$(1)' a real series?"; \
exit 1; \
}
endef

.PHONY: help
help: ## Show this help
@grep -hE '^[a-zA-Z0-9_.%-]+:.*## ' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS=":.*## "}; {printf "\033[36m%-18s\033[0m %s\n", $$1, $$2}'

.PHONY: seed-%
seed-%: ## Seed a site from the official image, e.g. make seed-5.2
$(call require_version,$*)
PLATFORM=$(PLATFORM) STOP_SIGNAL=$(STOP_SIGNAL_$*) \
./seed.sh $(BASE_$*) build/$*

# Defined BEFORE demo-%: macOS ships GNU Make 3.81, which takes the first
# matching pattern rather than the shortest stem (see ../legacy/Makefile).
.PHONY: demo-image-%
demo-image-%: ## Build only the -demo image from an existing seed, e.g. make demo-image-5.2
$(call require_version,$*)
@test -f build/$*/filestorage/Data.fs || { echo "No seed in build/$*/ — run make seed-$*"; exit 1; }
$(DOCKER) build \
--platform $(PLATFORM) \
--build-arg BASE_IMAGE=$(BASE_$*) \
--build-arg SEED=build/$* \
-t $(IMAGE_PREFIX):$(FULL_VERSION_$*)-demo \
-t $(IMAGE_PREFIX):$*-demo \
.

.PHONY: demo-%
demo-%: ## Seed and build the -demo image for a series, e.g. make demo-5.2
$(call require_version,$*)
$(MAKE) seed-$*
$(MAKE) demo-image-$*

.PHONY: test-demo-%
test-demo-%: ## Smoke-test a built -demo image, e.g. make test-demo-5.2
$(call require_version,$*)
SMOKE_EXPECT_SITE=1 \
SMOKE_LOG_SOURCE=$(LOG_SOURCE_$*) \
IMAGE=$(IMAGE_PREFIX):$(FULL_VERSION_$*)-demo \
../legacy/shared/smoke-test.sh

.PHONY: demos
demos: ## Build every -demo image (slow: each one boots Zope and creates a site)
@for v in $(SERIES); do $(MAKE) demo-$$v || exit 1; done

.PHONY: test-demos
test-demos: ## Smoke-test every built -demo image
@for v in $(SERIES); do $(MAKE) test-demo-$$v || exit 1; done

.PHONY: lint
lint: ## Lint the Dockerfile (hadolint) and shell scripts (shellcheck)
@echo "hadolint Dockerfile"
@$(DOCKER) run --rm -v "$(CURDIR):/mnt:ro" -w /mnt \
hadolint/hadolint hadolint Dockerfile
@echo "shellcheck *.sh"
@$(DOCKER) run --rm -v "$(CURDIR):/mnt:ro" -w /mnt koalaman/shellcheck:stable \
-s sh seed.sh demo-entrypoint.sh
@echo "lint OK"

.PHONY: clean
clean: ## Remove seeds and locally built -demo images
rm -rf build
-@$(DOCKER) rmi -f $(foreach v,$(SERIES),$(IMAGE_PREFIX):$(v)-demo $(IMAGE_PREFIX):$(FULL_VERSION_$(v))-demo) 2>/dev/null
76 changes: 76 additions & 0 deletions demo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# The `-demo` variants (Plone 4.3 - 5.2)

Each of the 4.3 - 5.2 series has a `-demo` image that **already has a Plone
site**, created at the id `Plone`. Start one and browse straight to
`http://localhost:8080/Plone`.

```sh
docker run -d -p 8080:8080 plone/plone:5.2-demo
open http://localhost:8080/Plone
```

| Series | Built on | Published tags |
|---|---|---|
| 5.2 | `plone:5.2.14` | `5.2-demo`, `5.2.14-demo` |
| 5.1 | `plone:5.1.6` | `5.1-demo`, `5.1.6-demo` |
| 5.0 | `plone:5.0.8` | `5.0-demo`, `5.0.8-demo` |
| 4.3 | `plone:4.3.19` | `4.3-demo`, `4.3.19-demo` |

The tags are on `plone/plone` and `ghcr.io/plone/plone.docker`, next to the
legacy `-demo` images for 1.0 - 4.2 (see [`../legacy/demo/`](../legacy/demo/)).

| | |
|---|---|
| Site id | `Plone` |
| Admin | `admin` / `admin` |
| Everything else | identical to the base image — same Zope, Python, add-ons, environment variables |

The admin password comes from the base images, which bake `admin:admin` into
their buildout and do not read `ADMIN_PASSWORD`. These are demo images for a
throwaway stack. **Do not put them anywhere reachable.**

## Building locally

```sh
cd demo
make demo-5.2 # seeds build/5.2/, then builds plone-demo:5.2.14-demo and :5.2-demo
make test-demo-5.2 # smoke test: the site must already be there on first start
```

`make demos` and `make test-demos` cover every series. Building needs `curl`
on the host, and runs the amd64-only images under emulation on Apple Silicon.

## How it works

The base images are the **official `plone` images**, pinned by digest in the
[`Makefile`](Makefile), and are not rebuilt: their Debian releases have left
`deb.debian.org`, so a rebuild would fail or produce a different image. For the
same reason nothing can be installed into them — they ship neither `curl` nor
`wget` — so the site is created from outside:

1. [`seed.sh`](seed.sh) starts the official image as it is, waits for HTTP,
and creates the site through
[`legacy/shared/create-plone-site.sh`](../legacy/shared/create-plone-site.sh),
the same script the legacy images use. It stops Zope with the signal that
closes the storage cleanly, checks that it did, and copies `Data.fs` and the
blob storage out with `docker cp`.
2. [`Dockerfile`](Dockerfile) adds that seed at `/app/seed`, plus
[`demo-entrypoint.sh`](demo-entrypoint.sh): on first start it copies the
seed into `/data`, then runs the base image's own `/docker-entrypoint.sh`
with the same arguments. When `/data` already holds a database nothing is
copied, so mounting a real site over a demo image still works.

## Validated facts

- **[V 2026-10-08]** **5.2 needs SIGINT to stop cleanly.** Its entrypoint
execs `bin/instance console`, which ignored SIGTERM until `docker stop`'s
timeout killed it — leaving no `Data.fs.index`, the evidence that the
storage was closed. On SIGINT it stops in about a second. 4.3 - 5.1 trap
SIGTERM and run `bin/instance stop`. See `STOP_SIGNAL_*` in the Makefile.
- **[V 2026-10-08]** **5.2 writes no log file.** It logs to stdout/stderr, so
the smoke test reads its product-load errors from `docker logs`
(`SMOKE_LOG_SOURCE=docker`); 4.3 - 5.1 log to `/data/log/instance.log`.
- **[V 2026-10-08]** **Plone 5's theme is not a skin.** The add-site form
forces `plonetheme.barceloneta:default`, a Diazo theme, so the skin check
`create-plone-site.sh` uses for Plone 4 finds nothing on Plone 5. The script
checks for `++theme++barceloneta` in the rendered page there instead.
Loading
Loading