🐳 Migrate penpot images to DHI (#10734)

Move Dockerfile.frontend, .backend, .exporter, .mcp and .storybook
under docker/images/ from ubuntu:26.04 / nginx-unprivileged / a
manual Node tarball install to Docker Hardened Images (Debian 13,
or Alpine for storybook). storybook and mcp get a true non-dev
runtime; frontend, backend and exporter keep the -dev tag as their
final image, since each needs a shell and/or package manager at
container runtime (nginx templating, fontforge/python3, and a
headless-browser stack, respectively).
This commit is contained in:
David Barragán Merino 2026-07-31 19:27:15 +02:00 committed by GitHub
parent e741313add
commit 97a833f1e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 76 additions and 124 deletions

View File

@ -107,6 +107,19 @@ jobs:
username: ${{ secrets.PUB_DOCKER_USERNAME }}
password: ${{ secrets.PUB_DOCKER_PASSWORD }}
# Frontend, backend, exporter, storybook and mcp now build FROM
# Docker Hardened Images (dhi.io). DHI is free (Apache 2.0, no
# subscription), but pulling from it still requires an
# authenticated login -- a separate `docker login` against a
# different registry host, even though it reuses the same
# PUB_DOCKER_* credentials as the DockerHub login above.
- name: Login to Docker Hardened Images registry (base image pull)
uses: docker/login-action@v4
with:
registry: dhi.io
username: ${{ secrets.PUB_DOCKER_USERNAME }}
password: ${{ secrets.PUB_DOCKER_PASSWORD }}
# Every build pushes an immutable `build-<key>` tag; if it already
# exists in the registry, this exact image was already built and the
# whole build can be skipped. Moving tags are handled by `promote`.
@ -164,6 +177,8 @@ jobs:
file: ./docker/images/Dockerfile.${{ matrix.image }}
platforms: linux/amd64,linux/arm64
push: true
provenance: mode=max
sbom: true
# Immutable tag only; branch tags are moved atomically for the
# whole image set by the `promote` job.
tags: ${{ secrets.DOCKER_REGISTRY }}/${{ matrix.image }}:build-${{ needs.prepare.outputs.build_key }}

View File

@ -1,4 +1,4 @@
FROM ubuntu:26.04 AS build
FROM dhi.io/debian-base:trixie-debian13-dev AS build
LABEL maintainer="Penpot <docker@penpot.app>"
ENV LANG='C.UTF-8' \
@ -47,7 +47,8 @@ RUN set -eux; \
--add-modules java.base,jdk.net,jdk.management.agent,java.se,jdk.compiler,jdk.javadoc,jdk.attach,jdk.unsupported,jdk.jfr,jdk.jcmd \
--output /opt/jre;
FROM ubuntu:26.04 AS image
FROM dhi.io/debian-base:trixie-debian13-dev AS image
LABEL maintainer="Penpot <docker@penpot.app>"
ENV LANG='C.UTF-8' \
@ -55,11 +56,13 @@ ENV LANG='C.UTF-8' \
JAVA_HOME="/opt/jre" \
PATH=/opt/jre/bin:/opt/imagick/bin:$PATH \
DEBIAN_FRONTEND=noninteractive \
TZ=Etc/UTC
TZ=Etc/UTC \
LD_LIBRARY_PATH=/opt/imagick/lib/deps
RUN set -ex; \
useradd -U -M -u 1001 -s /bin/false -d /opt/penpot penpot; \
apt-get -qq update; \
apt-get -qqy --no-install-recommends install passwd; \
useradd -U -M -u 1001 -s /bin/false -d /opt/penpot penpot; \
apt-get -qq dist-upgrade; \
apt-get -qqy --no-install-recommends install \
ca-certificates \
@ -71,7 +74,7 @@ RUN set -ex; \
libglib2.0-0 \
libgomp1 \
libheif1 \
libjpeg-turbo8 \
libjpeg62-turbo \
liblcms2-2 \
libopenexr-3-1-30 \
libopenjp2-7 \
@ -81,7 +84,7 @@ RUN set -ex; \
libwebp7 \
libwebpdemux2 \
libwebpmux3 \
libxml2-16 \
libxml2 \
libzip5 \
libzstd1 \
python3 \
@ -100,7 +103,7 @@ RUN set -ex; \
chown -R penpot:penpot /opt/data;
COPY --from=build /opt/jre /opt/jre
COPY --from=penpotapp/imagemagick:7.1.2-24 /opt/imagick /opt/imagick
COPY --from=penpotapp/imagemagick:7.1.2-27 /opt/imagick /opt/imagick
COPY files/imagemagick-policy.xml /opt/imagick/etc/ImageMagick-7/policy.xml

View File

@ -1,18 +1,16 @@
FROM ubuntu:26.04
FROM dhi.io/node:24.18.1-debian13-dev
LABEL maintainer="Penpot <docker@penpot.app>"
ENV LANG=en_US.UTF-8 \
LC_ALL=en_US.UTF-8 \
NODE_VERSION=v24.18.1 \
DEBIAN_FRONTEND=noninteractive \
PATH=/opt/node/bin:/opt/imagick/bin:$PATH \
PATH=/opt/imagick/bin:$PATH \
PLAYWRIGHT_BROWSERS_PATH=/opt/penpot/browsers
RUN set -ex; \
useradd -U -M -u 1001 -s /bin/false -d /opt/penpot penpot; \
mkdir -p /etc/resolvconf/resolv.conf.d; \
echo "nameserver 127.0.0.11" > /etc/resolvconf/resolv.conf.d/tail; \
apt-get -qq update; \
apt-get -qqy --no-install-recommends install passwd; \
useradd -U -M -u 1001 -s /bin/false -d /opt/penpot penpot; \
apt-get -qq dist-upgrade; \
apt-get -qqy --no-install-recommends install \
curl \
@ -44,16 +42,16 @@ RUN set -ex; \
fonts-freefont-ttf \
poppler-utils \
\
libasound2t64 \
libatk-bridge2.0-0t64 \
libatk1.0-0t64 \
libatspi2.0-0t64 \
libasound2 \
libatk-bridge2.0-0 \
libatk1.0-0 \
libatspi2.0-0 \
libcairo2 \
libcups2t64 \
libcups2 \
libdbus-1-3 \
libdrm2 \
libgbm1 \
libglib2.0-0t64 \
libglib2.0-0 \
libnspr4 \
libnss3 \
libpango-1.0-0 \
@ -68,7 +66,7 @@ RUN set -ex; \
\
libgomp1 \
libheif1 \
libjpeg-turbo8 \
libjpeg62-turbo \
liblcms2-2 \
libopenexr-3-1-30 \
libopenjp2-7 \
@ -78,44 +76,26 @@ RUN set -ex; \
libwebp7 \
libwebpdemux2 \
libwebpmux3 \
libxml2-16 \
libxml2 \
libzip5 \
libzstd1 \
; \
apt-get clean; \
rm -rf /var/lib/apt/lists/*;
RUN set -eux; \
ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \
aarch64|arm64) \
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-arm64.tar.gz"; \
;; \
amd64|x86_64) \
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64.tar.gz"; \
;; \
*) \
echo "Unsupported arch: ${ARCH}"; \
exit 1; \
;; \
esac; \
curl -LfsSo /tmp/nodejs.tar.gz ${BINARY_URL}; \
mkdir -p /opt/node; \
cd /opt/node; \
tar -xf /tmp/nodejs.tar.gz --strip-components=1; \
chown -R root /opt/node; \
rm -rf /tmp/nodejs.tar.gz; \
corepack enable; \
rm -rf /var/lib/apt/lists/*; \
mkdir -p /opt/penpot; \
chown -R penpot:penpot /opt/penpot;
ARG BUNDLE_PATH="./bundle-exporter/"
COPY --chown=penpot:penpot $BUNDLE_PATH /opt/penpot/exporter/
COPY --from=penpotapp/imagemagick:7.1.2-24 /opt/imagick /opt/imagick
COPY --from=penpotapp/imagemagick:7.1.2-27 /opt/imagick /opt/imagick
WORKDIR /opt/penpot/exporter
# DHI Node image installs Node at the system level (symlinked into
# /usr/bin), so `./setup`'s internal `corepack enable` needs root to write
# there. Ownership is fixed right after.
RUN ./setup && chown -R penpot:penpot /opt/penpot/exporter
USER penpot:penpot
RUN ./setup
CMD ["node", "app.js"]

View File

@ -1,15 +1,17 @@
FROM nginxinc/nginx-unprivileged:1.31.1-alpine
FROM dhi.io/nginx:1.31.1-debian13-dev
LABEL maintainer="Penpot <docker@penpot.app>"
ENV DEBIAN_FRONTEND=noninteractive
USER root
RUN set -ex; \
apk update; \
apk upgrade; \
apk add --no-cache bash gettext; \
rm -rf /var/cache/apk/*; \
addgroup -g 1001 penpot; \
adduser -D -H -u 1001 -s /bin/false -h /opt/penpot -G penpot penpot; \
apt-get -qq update; \
apt-get -qq -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" dist-upgrade; \
apt-get -qqy -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" --no-install-recommends install bash gettext-base passwd; \
rm -rf /var/lib/apt/lists/*; \
groupadd -f -g 1001 penpot; \
useradd -M -u 1001 -s /usr/sbin/nologin -d /opt/penpot -g penpot penpot; \
mkdir -p /opt/data/assets; \
chown -R penpot:penpot /opt/data; \
mkdir -p /etc/nginx/overrides/main.d/; \

View File

@ -1,61 +1,26 @@
FROM ubuntu:26.04
FROM dhi.io/node:24.18.1-debian13-dev AS build
LABEL maintainer="Penpot <docker@penpot.app>"
ENV DEBIAN_FRONTEND=noninteractive
ARG BUNDLE_PATH="./bundle-mcp/"
COPY $BUNDLE_PATH /opt/penpot/mcp/
WORKDIR /opt/penpot/mcp
RUN ./setup
FROM dhi.io/node:24.18.1-debian13 AS image
LABEL maintainer="Penpot <docker@penpot.app>"
ENV LANG=en_US.UTF-8 \
LC_ALL=en_US.UTF-8 \
NODE_VERSION=v24.18.1 \
DEBIAN_FRONTEND=noninteractive \
PATH=/opt/node/bin:$PATH \
PENPOT_MCP_SERVER_HOST=0.0.0.0
RUN set -ex; \
useradd -U -M -u 1001 -s /bin/false -d /opt/penpot penpot; \
mkdir -p /etc/resolvconf/resolv.conf.d; \
echo "nameserver 127.0.0.11" > /etc/resolvconf/resolv.conf.d/tail; \
apt-get -qq update; \
apt-get -qq dist-upgrade; \
apt-get -qqy --no-install-recommends install \
curl \
tzdata \
locales \
ca-certificates \
; \
apt-get clean; \
rm -rf /var/lib/apt/lists/*; \
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen; \
locale-gen; \
find /usr/share/i18n/locales/ -type f ! -name "en_US" ! -name "POSIX" ! -name "C" -delete;
RUN set -eux; \
ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \
aarch64|arm64) \
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-arm64.tar.gz"; \
;; \
amd64|x86_64) \
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64.tar.gz"; \
;; \
*) \
echo "Unsupported arch: ${ARCH}"; \
exit 1; \
;; \
esac; \
curl -LfsSo /tmp/nodejs.tar.gz ${BINARY_URL}; \
mkdir -p /opt/node; \
cd /opt/node; \
tar -xf /tmp/nodejs.tar.gz --strip-components=1; \
chown -R root /opt/node; \
rm -rf /tmp/nodejs.tar.gz; \
corepack enable; \
mkdir -p /opt/penpot; \
chown -R penpot:penpot /opt/penpot;
ARG BUNDLE_PATH="./bundle-mcp/"
COPY --chown=penpot:penpot $BUNDLE_PATH /opt/penpot/mcp/
COPY --from=build --chown=node:node /opt/penpot/mcp /opt/penpot/mcp
WORKDIR /opt/penpot/mcp
USER penpot:penpot
RUN ./setup
USER node
CMD ["node", "index.js", "--multi-user"]

View File

@ -1,24 +1,11 @@
FROM nginxinc/nginx-unprivileged:1.31.1-alpine
FROM dhi.io/nginx:1.31.1-alpine3.22
LABEL maintainer="Penpot <docker@penpot.app>"
USER root
RUN set -ex; \
apk update; \
apk upgrade; \
rm -rf /var/cache/apk/*; \
addgroup -g 1001 penpot; \
adduser -D -H -u 1001 -s /bin/false -h /opt/penpot -G penpot penpot;
ARG BUNDLE_PATH="./bundle-storybook/"
COPY $BUNDLE_PATH /var/www/
COPY ./files/nginx.storybook.conf /etc/nginx/conf.d/default.conf
RUN chown -R 1001:0 /var/cache/nginx; \
chmod -R g+w /var/cache/nginx; \
chown -R 1001:0 /etc/nginx; \
chmod -R g+w /etc/nginx; \
chown -R 1001:0 /var/www; \
chmod -R g+w /var/www;
COPY --chown=65532:65532 $BUNDLE_PATH /var/www/
COPY --chown=65532:65532 ./files/nginx.storybook.conf /etc/nginx/conf.d/default.conf
USER penpot:penpot
# Already the default user in the DHI runtime; explicit for clarity. Numeric
# UID, not by name -- this image has no "nonroot" entry in /etc/passwd.
USER 65532:65532