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
5 changes: 4 additions & 1 deletion .github/workflows/legacy-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ jobs:
- 'legacy/demo/**'
- 'legacy/Makefile'
- '.github/workflows/legacy-build.yml'
v3.0:
- *shared
- 'legacy/3.0/**'
v3.1:
- *shared
- 'legacy/3.1/**'
Expand Down Expand Up @@ -64,7 +67,7 @@ jobs:
BUILD_ALL: ${{ github.event_name != 'pull_request' }}
run: |
# Implemented versions; append when a new directory lands.
ALL='["3.1","3.2","3.3","4.0","4.1","4.2"]'
ALL='["3.0","3.1","3.2","3.3","4.0","4.1","4.2"]'
if [ "${BUILD_ALL}" = "true" ]; then
VERSIONS="${ALL}"
else
Expand Down
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Changelog
- Add Plone 3.0 image to the legacy matrix. Refs #187
[@ericof]
- Add Plone 3.1 image to the legacy matrix. Refs #186
[@ericof]
- Add Plone 3.2 image to the legacy matrix. Refs #185
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ listed; earlier point releases remain in the repository and on Docker Hub.
| 3.3.6 | 2.4.6 | Debian *(legacy)* | [`legacy/3.3/Dockerfile`](legacy/3.3/Dockerfile) | `3.3`, `3.3.6`, `3.3-demo`, `3.3.6-demo` |
| 3.2.3 | 2.4.6 | Debian *(legacy)* | [`legacy/3.2/Dockerfile`](legacy/3.2/Dockerfile) | `3.2`, `3.2.3`, `3.2-demo`, `3.2.3-demo` |
| 3.1.7 | 2.4.6 | Debian *(legacy)* | [`legacy/3.1/Dockerfile`](legacy/3.1/Dockerfile) | `3.1`, `3.1.7`, `3.1-demo`, `3.1.7-demo` |
| 3.0.5 | 2.4.6 | Debian *(legacy)* | [`legacy/3.0/Dockerfile`](legacy/3.0/Dockerfile) | `3.0`, `3.0.5`, `3.0-demo`, `3.0.5-demo` |

### Where the tags live

Expand Down
232 changes: 232 additions & 0 deletions legacy/3.0/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
# syntax=docker/dockerfile:1
#
# Plone 3.0.x — the LAST tarball-era release (Zope 2.10.x / Python 2.4.x)
#
# Strategy:
# Stage 1 (fetch): modern userland downloads everything (old TLS never touches network)
# Stage 2 (build): Python 2.4 + PIL + ElementTree + Zope 2.10 on bookworm
# Stage 3 (runtime): slim image, non-root, /data volume, port 8080
#
# Two things differ from 2.0/2.1/2.5, both verified rather than assumed:
# * the bundle is Plone-3.0.5/{Products,lib/python}, not one dir per product,
# so lib/python must ALSO be installed (see the install step below);
# * ElementTree is a new hard dependency (see shared/build-elementtree.sh).
#
# NOT FOR PRODUCTION. Zope 2.10 has known, unpatched security issues.
# Intended for content extraction, migration, and archaeology only.

ARG PYTHON_VERSION=2.4.6
ARG ZOPE_VERSION=2.10.13
# [V 2026-08-20] 3.0.5 is the last FULL bundle, not 3.0.6. dist.plone.org has
# PloneBase-3.0.6.tar.gz but no Plone-3.0.6.tar.gz: the 3.0.6 roll shipped only
# as PloneBase, which is the stripped core (CMFPlone alone, 1.0 MB) WITHOUT the
# dependency products. The last full Plone-*.tar.gz for the 3.0 series is
# 3.0.5 (12.6 MB, 41 products), and the repo convention is to always use the
# full bundle. /archive/ holds only UnifiedInstaller tarballs for 3.0.x; the
# plain bundles live at the dist.plone.org root.
ARG PLONE_VERSION=3.0.5
ARG PIL_VERSION=1.1.6
ARG ELEMENTTREE_VERSION=1.2.7-20070827-preview

# [V 2026-08-20] Verified: Zope-2.10.13-final.tgz (7.4 MB) exists at
# old.zope.dev, unpacks to Zope-2.10.13-final/ with a top-level ./configure,
# and its configure declares TARGET="2.4.6" — exactly the interpreter these
# images already build. Plone 3.0's INSTALL.txt requires Zope 2.10.4+.
ARG ZOPE_URL=https://old.zope.dev/Products/Zope/${ZOPE_VERSION}/Zope-${ZOPE_VERSION}-final.tgz
# [V] python.org keeps all historical releases
ARG PYTHON_URL=https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz
# [V 2026-08-20] Verified: Plone-3.0.5.tar.gz at the dist.plone.org root
# (NOT under /archive/, unlike the 2.x bundles).
ARG PLONE_URL=https://dist.plone.org/Plone-${PLONE_VERSION}.tar.gz
# [V 2026-08-20] effbot.org is dead (404) and PyPI lists PIL but serves no
# files; dist.plone.org/thirdparty is the surviving canonical source.
ARG PIL_URL=https://dist.plone.org/thirdparty/PIL-${PIL_VERSION}.tar.gz
# [V 2026-08-20] Plone's own docs point at effbot.org for this, which is dead.
# The same thirdparty mirror carries it — as a .zip, hence unzip below.
ARG ELEMENTTREE_URL=https://dist.plone.org/thirdparty/elementtree-${ELEMENTTREE_VERSION}.zip

# --- simplejson -------------------------------------------------------------
# Python has no stdlib `json` before 2.6, so every image below 4.0 ships
# simplejson instead. The release is NOT interchangeable between interpreters
# — see the header of shared/install-simplejson.sh, and note that the install
# gate will fail the build on a wrong pairing rather than let it through.
ARG SIMPLEJSON_VERSION=2.0.9
ARG SIMPLEJSON_URL=https://files.pythonhosted.org/packages/source/s/simplejson/simplejson-${SIMPLEJSON_VERSION}.tar.gz

# ---------------------------------------------------------------------------
# Stage 1: fetch — modern TLS, nothing else
# ---------------------------------------------------------------------------
FROM debian:bookworm-slim AS fetch
ARG ZOPE_URL
ARG PYTHON_URL
ARG PLONE_URL
ARG PIL_URL
ARG ELEMENTTREE_URL
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates curl \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /dist
RUN curl -fSL "${PYTHON_URL}" -o python.tgz \
&& curl -fSL "${ZOPE_URL}" -o zope.tgz \
&& curl -fSL "${PLONE_URL}" -o plone.tar.gz \
&& curl -fSL "${PIL_URL}" -o pil.tar.gz \
&& curl -fSL "${ELEMENTTREE_URL}" -o elementtree.zip

# ---------------------------------------------------------------------------
# Stage 1b: fetch-simplejson — derived from fetch, deliberately not part of it
# ---------------------------------------------------------------------------
# pybuild does `COPY --from=fetch /dist /dist`, so folding this download into
# stage 1 would invalidate the interpreter build every time the simplejson pin
# moves and recompile CPython from source — hours, under emulation on an arm64
# host. Deriving a stage leaves `fetch` byte-identical, so pybuild stays cached,
# and reuses the curl and ca-certificates already installed there. WORKDIR
# /dist is inherited, so the tarball lands beside the others.
FROM fetch AS fetch-simplejson
ARG SIMPLEJSON_URL
RUN curl -fSL --retry 5 --retry-delay 5 "${SIMPLEJSON_URL}" -o simplejson.tar.gz

# ---------------------------------------------------------------------------
# Stage 2a: pybuild — Python 2.4 only, so `--target pybuild` is exactly gate G1
# and a Zope failure cannot deny us a testable interpreter.
# ---------------------------------------------------------------------------
FROM debian:bookworm AS pybuild
ARG PYTHON_VERSION

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
gcc make libc6-dev zlib1g-dev libcrypt-dev \
&& rm -rf /var/lib/apt/lists/*

COPY --from=fetch /dist /dist
COPY shared/build-python2.sh /usr/local/bin/build-python2.sh

RUN build-python2.sh /dist/python.tgz "${PYTHON_VERSION}" /opt/python2.4

# ---------------------------------------------------------------------------
# Stage 2b: build — PIL, ElementTree, Zope 2.10, the instance, and Plone.
# ---------------------------------------------------------------------------
FROM pybuild AS build
ARG ZOPE_VERSION
ARG PLONE_VERSION
ARG PIL_VERSION
ARG ELEMENTTREE_VERSION

RUN apt-get update \
&& apt-get install -y --no-install-recommends libjpeg62-turbo-dev unzip \
&& rm -rf /var/lib/apt/lists/*

# --- PIL --------------------------------------------------------------------
COPY shared/build-pil.sh /usr/local/bin/build-pil.sh
RUN build-pil.sh /dist/pil.tar.gz "${PIL_VERSION}" /opt/python2.4/bin/python2.4

# --- ElementTree ------------------------------------------------------------
COPY shared/build-elementtree.sh /usr/local/bin/build-elementtree.sh
RUN build-elementtree.sh /dist/elementtree.zip "${ELEMENTTREE_VERSION}" \
/opt/python2.4/bin/python2.4

# --- Zope 2.10 --------------------------------------------------------------
# Same configure + make as 2.7/2.8/2.9. -fcommon avoids gcc>=10 "multiple
# definition" errors from tentative definitions in the old C extensions.
RUN tar -xzf /dist/zope.tgz -C /tmp \
&& cd /tmp/Zope-${ZOPE_VERSION}-final \
&& ./configure --prefix=/opt/zope \
--with-python=/opt/python2.4/bin/python2.4 \
&& CFLAGS="-fcommon -fno-strict-aliasing" make \
&& make install \
&& rm -rf /tmp/Zope-${ZOPE_VERSION}-final

# --- Zope instance + Plone products -----------------------------------------
# Placeholder inituser; the real one is written by the entrypoint at runtime.
RUN /opt/python2.4/bin/python2.4 /opt/zope/bin/mkzopeinstance.py \
--dir /app/instance --user "admin:placeholder"

# [V 2026-08-20] The 3.0 bundle layout differs from every 2.x bundle:
#
# Plone-3.0.5/
# Products/<41 product dirs>
# lib/python/{archetypes,five,kss,plone,wicked}
# INSTALL.txt README.txt RELEASENOTES.txt
#
# so Products/ and lib/python/ must be installed to their counterparts in the
# instance, per INSTALL.txt: "The Zope Products are installed in the usual
# location: the Products directory of the Zope instance. Python package may be
# installed in the lib/python directory in the Zope instance".
#
# [V 2026-08-20] <instance>/lib/python IS on sys.path: Zope 2.10's
# Zope2/Startup/handlers.py:187 says "always insert instancehome/lib/python",
# and mkzopeinstance creates that directory in the skeleton.
#
# Both copies are asserted, so a layout change in a future bundle fails the
# build instead of yielding an instance that starts with half of Plone missing.
RUN mkdir -p /tmp/plone \
&& tar -xzf /dist/plone.tar.gz -C /tmp/plone --strip-components=1 \
&& test -d /tmp/plone/Products \
&& test -d /tmp/plone/lib/python \
&& cp -a /tmp/plone/Products/. /app/instance/Products/ \
&& cp -a /tmp/plone/lib/python/. /app/instance/lib/python/ \
&& test -f /app/instance/Products/CMFPlone/version.txt \
&& test -d /app/instance/lib/python/plone \
&& rm -rf /tmp/plone

# Point the filestorage and logs at /data (populated at runtime).
RUN sed -i \
-e 's|\$INSTANCE/var/Data.fs|/data/filestorage/Data.fs|' \
-e 's|\$INSTANCE/log|/data/log|' \
/app/instance/etc/zope.conf

# --- simplejson -------------------------------------------------------------
# Last in the stage, and with its ARG declared here rather than at the top, so
# that moving the pin does not invalidate the PIL and Plone layers above.
# site-packages lives under /opt/python2.4, which stage 3 copies wholesale, so
# nothing further is needed at runtime.
ARG SIMPLEJSON_VERSION
COPY --from=fetch-simplejson /dist/simplejson.tar.gz /dist/simplejson.tar.gz
COPY shared/install-simplejson.sh /usr/local/bin/install-simplejson.sh
RUN install-simplejson.sh /dist/simplejson.tar.gz "${SIMPLEJSON_VERSION}" \
/opt/python2.4/bin/python2.4

# ---------------------------------------------------------------------------
# Stage 3: runtime
# ---------------------------------------------------------------------------
FROM debian:bookworm-slim
ARG PLONE_VERSION
ARG ZOPE_VERSION
ARG PYTHON_VERSION

LABEL org.opencontainers.image.title="Plone ${PLONE_VERSION} (legacy)" \
org.opencontainers.image.description="Plone ${PLONE_VERSION} on Zope ${ZOPE_VERSION} / Python ${PYTHON_VERSION}. Migration and content-extraction use only — NOT for production." \
org.opencontainers.image.vendor="Simples Consultoria"

RUN apt-get update \
&& apt-get install -y --no-install-recommends zlib1g libcrypt1 libjpeg62-turbo \
&& rm -rf /var/lib/apt/lists/* \
&& useradd --system -m -d /app -U -u 500 plone \
&& mkdir -p /data \
&& chown -R plone:plone /data

COPY --from=build --chown=plone:plone /opt/python2.4 /opt/python2.4
COPY --from=build --chown=plone:plone /opt/zope /opt/zope
COPY --from=build --chown=plone:plone /app /app
COPY --chown=plone:plone shared/docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
COPY --chown=plone:plone shared/upgrade-plone.py /app/upgrade-plone.py
COPY --chown=plone:plone shared/configure-zeo.sh /app/configure-zeo.sh
RUN chmod +x /app/configure-zeo.sh

ENV ZOPE_HOME=/opt/zope \
INSTANCE_HOME=/app/instance \
PYTHON=/opt/python2.4/bin/python2.4 \
ZOPE_HTTP_PORT=8080

VOLUME /data
EXPOSE 8080
WORKDIR /app
USER plone

# Plone 3 does considerably more work at first start than 2.x (Five/ZCML
# registration across 41 products), so the warm-up window is longer.
HEALTHCHECK --interval=10s --timeout=5s --start-period=180s --retries=6 \
CMD ["/opt/python2.4/bin/python2.4", "-c", "import urllib; urllib.urlopen('http://127.0.0.1:8080/')"]

ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["start"]
115 changes: 115 additions & 0 deletions legacy/3.0/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Plone 3.0 legacy image

Plone 3.0.5 on Zope 2.10.13 / Python 2.4.6. The **last tarball-era release** —
3.2 onwards require buildout, which needs a different template.

**Not for production.** Zope 2.10 has known, unpatched vulnerabilities. This
image exists for content extraction, migration rehearsal, and historical
inspection.

## Usage

```sh
docker build -f 3.0/Dockerfile -t plone-legacy:3.0.5 .
docker run -d -p 8080:8080 -e ADMIN_PASSWORD=secret \
-v plone30-data:/data plone-legacy:3.0.5
```

| | |
|---|---|
| Port | 8080 (`ZOPE_HTTP_PORT` to change) |
| Volume | `/data` (`filestorage/Data.fs`, `log/`) |
| User | `plone` (uid 500) |
| Env | `ADMIN_USER`, `ADMIN_PASSWORD` (first run only) |
| PIL | 1.1.6, with JPEG + PNG (no freetype/`_imagingft`) |
| ElementTree | 1.2.7 (required by Plone 3; see below) |

## Two ways this differs from the 2.x images

Both were verified against the actual bundle, not assumed from the 2.x pattern.

### 1. The bundle has a different shape

Where every 2.x bundle was one directory per product at the top level, 3.0 is:

```
Plone-3.0.5/
Products/<41 product dirs>
lib/python/{archetypes,five,kss,plone,wicked}
INSTALL.txt README.txt RELEASENOTES.txt
```

`INSTALL.txt` is explicit: *"The Zope Products are installed in the usual
location: the Products directory of the Zope instance. Python package may be
installed in the lib/python directory in the Zope instance"*. Both copies are
made, and both are asserted in the Dockerfile — a `--strip-components=1` copy
of the top level alone would produce an instance that starts with half of
Plone missing and no error.

`<instance>/lib/python` is genuinely on `sys.path`: Zope 2.10's
`Zope2/Startup/handlers.py:187` reads *"always insert instancehome/lib/python"*,
and `mkzopeinstance` creates that directory in its skeleton.

### 2. ElementTree is a new hard dependency

`INSTALL.txt` says *"The python ElementTree package is now required"*, and it
means it. `CMFPlone/setup/dependencies.py` only logs when it is missing, but
`Products/Marshall/handlers/atxml.py:51-54` does

```python
try:
from celementtree import ElementTree
except ImportError:
from elementtree import ElementTree
```

with no outer guard, so with neither package installed the module raises
`ImportError` and Marshall fails to load. Python 2.5 absorbed this as
`xml.etree`, but these images run 2.4.

Plone's docs point at `effbot.org` for the download, which is dead (404) — the
same dead end as PIL. `dist.plone.org/thirdparty/` carries
`elementtree-1.2.7-20070827-preview.zip`, and that is what is installed.

## Validated facts / hypotheses

- **[V 2026-08-20]** **3.0.5 is the last full bundle, not 3.0.6.** The matrix
listed `3.0.6*`. `PloneBase-3.0.6.tar.gz` exists (1.0 MB) but there is no
`Plone-3.0.6.tar.gz`: the 3.0.6 roll shipped only as PloneBase, the stripped
core without dependency products. The last full `Plone-*.tar.gz` is 3.0.5
(12.6 MB, 41 products). Note also that unlike the 2.x bundles these live at
the **dist.plone.org root**, not under `/archive/`, which holds only
UnifiedInstaller tarballs for this series.
- **[V 2026-08-20]** `Zope-2.10.13-final.tgz` (7.4 MB) is downloadable from
`old.zope.dev`, unpacks to `Zope-2.10.13-final/` with a top-level
`./configure`, and that configure declares `TARGET="2.4.6"` — exactly the
interpreter these images already build. Plone 3.0 requires Zope 2.10.4+.
- **[V 2026-08-20]** Python 2.4.6, PIL 1.1.6 and Zope 2.10.13 all build through
the shared scripts with no 3.0-specific changes. `-fcommon` again sufficient.
- **[V 2026-08-20]** **A guard failed for the wrong reason: CRLF.** The
elementtree zip ships `setup.py` with `\r\n` endings, so every line really
ends `...setup\r`. GNU grep anchors `$` after the `\n` only, so the strict
pattern `'^from distutils.core import setup$'` matched **zero** times and the
build failed on its own assertion. Confirmed in-container: GNU grep 3.8 on
bookworm returns 0 for the strict pattern and 1 once `\r` is accounted for —
while the developer's host grep is lenient and matched, hiding it.
`build-elementtree.sh` now normalises line endings before asserting.
**Test text guards against the container's grep, not the host's.**
- **[V 2026-08-20]** `old.zope.dev` is intermittently slow and returned a
Cloudflare **522** on the first build attempt, then served the same URL fine
(16.6 s) on retry. Nothing in the image is wrong when this happens — just
build again. Worth knowing before debugging a phantom failure.
- **[V 2026-08-20]** The site factory matches 2.5, not 2.1: `/manage_main`
links `CMFPlone/addPloneSiteForm`, whose form posts to `addPloneSite`. The
smoke test's discovery handled 3.0 with no change.
- **[V 2026-08-20]** 42 entries land in `Products/` (41 product dirs plus a
loose file) and 6 in `lib/python`. Zero products fail to import or install,
and a Plone 3.0 site can be created and rendered (~20 kB carrying the Plone
`generator` meta tag). Passes twice consecutively.

## Smoke test (CI gate)

```sh
make test-3.0 # HTTP + auth + every product loads
SMOKE_CREATE_SITE=1 make test-3.0 # also creates a Plone site and checks its markup
```
Loading