Files
pgadmin4/Dockerfile
T
Ashesh Vashi 0d11dbc173 fix(docker): make CAP_NET_BIND_SERVICE optional for restricted runtimes (#9985)
The container previously applied CAP_NET_BIND_SERVICE to the python
interpreter so the non-root pgadmin user could bind to ports 80/443.
Some platforms refuse to honor file capabilities:

  - --cap-drop=ALL / OpenShift restricted-v2 SCC zero the bounding set,
    so the kernel returns EPERM on exec of any capability-tagged binary.
    This makes the image fail to start (issue #9657).
  - --security-opt=no-new-privileges / allowPrivilegeEscalation: false
    causes the kernel to silently strip file capabilities on exec, so
    the binary runs but a subsequent bind() to <1024 still fails.

Split the interpreter so neither default behavior nor restricted-runtime
support has to give up the other:

  - Dockerfile copies python3.X to /usr/local/bin/python3-cap and applies
    setcap to the copy. /usr/local/bin/python3.X stays un-capped, so
    /venv/bin/python3 (which symlinks to it) execs cleanly under
    restricted SCCs. A parallel /venv/bin/python3-cap symlink keeps the
    venv activation working when the capped interpreter is used.
  - entrypoint.sh reads /proc/self/status at startup. If NoNewPrivs is
    set, or CAP_NET_BIND_SERVICE is missing from the bounding set,
    gunicorn is invoked through the un-capped python and (when
    PGADMIN_LISTEN_PORT is unset) the default port falls back to 8080
    for plain HTTP or 8443 for TLS. A startup message records the
    choice.
  - Existing deployments with the default 80/443 mapping are unaffected:
    on every unrestricted runtime the bounding set still contains
    NET_BIND_SERVICE and gunicorn runs through the capped interpreter
    exactly as before.
  - PGADMIN_LISTEN_PORT, if set, is honored in both paths.

Docs gain a "Restricted Security Contexts" subsection covering the new
auto-detected fallback and the OpenShift / --cap-drop=ALL invocation.

Fixes #9657
2026-05-28 19:31:55 +05:30

221 lines
7.8 KiB
Docker

########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2026, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
#########################################################################
#########################################################################
# Create a Node container which will be used to build the JS components
# and clean up the web/ source code
#########################################################################
FROM alpine:latest AS app-builder
RUN apk add --no-cache \
autoconf \
automake \
bash \
g++ \
git \
libc6-compat \
libjpeg-turbo-dev \
libpng-dev \
libtool \
make \
nasm \
nodejs \
npm \
yarn \
zlib-dev
# Create the /pgadmin4 directory and copy the source into it
COPY web /pgadmin4/web
WORKDIR /pgadmin4/web
# Build the JS vendor code in the app-builder, and then remove the vendor source.
RUN --mount=type=bind,source=.git,target=/pgadmin4/.git \
--mount=type=tmpfs,target=node_modules \
--mount=type=tmpfs,target=pgadmin/static/js/generated/.cache \
export CPPFLAGS="-DPNG_ARM_NEON_OPT=0" && \
npm install -g corepack && \
corepack enable && \
yarn set version berry && \
yarn set version 4 && \
yarn install && \
yarn run bundle && \
rm -rf yarn.lock \
package.json \
.[^.]* \
babel.cfg \
webpack.* \
jest.config.js \
babel.*
#########################################################################
# Next, create the base environment for Python
#########################################################################
FROM python:3-alpine AS env-builder
# Install dependencies
RUN apk add --no-cache \
make && \
apk add --no-cache --virtual build-deps \
build-base \
openssl-dev \
libffi-dev \
postgresql-dev \
krb5-dev \
rust \
cargo \
zlib-dev \
libjpeg-turbo-dev \
libpng-dev
COPY requirements.txt /
RUN python3 -m venv --system-site-packages --without-pip /venv && \
/venv/bin/python3 -m pip install --no-cache-dir -r requirements.txt && \
apk del --no-cache build-deps
#########################################################################
# Now, create a documentation build container for the Sphinx docs
#########################################################################
FROM env-builder AS docs-builder
# Install Sphinx
RUN /venv/bin/python3 -m pip install --no-cache-dir sphinx
RUN /venv/bin/python3 -m pip install --no-cache-dir sphinxcontrib-youtube
# Copy the docs from the local tree. Explicitly remove any existing builds that
# may be present
COPY docs /pgadmin4/docs
COPY web /pgadmin4/web
RUN rm -rf /pgadmin4/docs/en_US/_build
# Build the docs
RUN LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 /venv/bin/sphinx-build /pgadmin4/docs/en_US /pgadmin4/docs/en_US/_build/html
# Cleanup unwanted files
RUN rm -rf /pgadmin4/docs/en_US/_build/html/.doctrees
RUN rm -rf /pgadmin4/docs/en_US/_build/html/_sources
RUN rm -rf /pgadmin4/docs/en_US/_build/html/_static/*.png
#########################################################################
# Create additional builders to get all of the PostgreSQL utilities
#########################################################################
FROM postgres:13-alpine AS pg13-builder
FROM postgres:14-alpine AS pg14-builder
FROM postgres:15-alpine AS pg15-builder
FROM postgres:16-alpine AS pg16-builder
FROM postgres:17-alpine AS pg17-builder
FROM postgres:18-alpine AS pg18-builder
FROM alpine:latest AS tool-builder
# Copy the PG binaries
COPY --from=pg13-builder /usr/local/bin/pg_dump /usr/local/pgsql/pgsql-13/
COPY --from=pg13-builder /usr/local/bin/pg_dumpall /usr/local/pgsql/pgsql-13/
COPY --from=pg13-builder /usr/local/bin/pg_restore /usr/local/pgsql/pgsql-13/
COPY --from=pg13-builder /usr/local/bin/psql /usr/local/pgsql/pgsql-13/
COPY --from=pg14-builder /usr/local/bin/pg_dump /usr/local/pgsql/pgsql-14/
COPY --from=pg14-builder /usr/local/bin/pg_dumpall /usr/local/pgsql/pgsql-14/
COPY --from=pg14-builder /usr/local/bin/pg_restore /usr/local/pgsql/pgsql-14/
COPY --from=pg14-builder /usr/local/bin/psql /usr/local/pgsql/pgsql-14/
COPY --from=pg15-builder /usr/local/bin/pg_dump /usr/local/pgsql/pgsql-15/
COPY --from=pg15-builder /usr/local/bin/pg_dumpall /usr/local/pgsql/pgsql-15/
COPY --from=pg15-builder /usr/local/bin/pg_restore /usr/local/pgsql/pgsql-15/
COPY --from=pg15-builder /usr/local/bin/psql /usr/local/pgsql/pgsql-15/
COPY --from=pg16-builder /usr/local/bin/pg_dump /usr/local/pgsql/pgsql-16/
COPY --from=pg16-builder /usr/local/bin/pg_dumpall /usr/local/pgsql/pgsql-16/
COPY --from=pg16-builder /usr/local/bin/pg_restore /usr/local/pgsql/pgsql-16/
COPY --from=pg16-builder /usr/local/bin/psql /usr/local/pgsql/pgsql-16/
COPY --from=pg17-builder /usr/local/bin/pg_dump /usr/local/pgsql/pgsql-17/
COPY --from=pg17-builder /usr/local/bin/pg_dumpall /usr/local/pgsql/pgsql-17/
COPY --from=pg17-builder /usr/local/bin/pg_restore /usr/local/pgsql/pgsql-17/
COPY --from=pg17-builder /usr/local/bin/psql /usr/local/pgsql/pgsql-17/
COPY --from=pg18-builder /usr/local/bin/pg_dump /usr/local/pgsql/pgsql-18/
COPY --from=pg18-builder /usr/local/bin/pg_dumpall /usr/local/pgsql/pgsql-18/
COPY --from=pg18-builder /usr/local/bin/pg_restore /usr/local/pgsql/pgsql-18/
COPY --from=pg18-builder /usr/local/bin/psql /usr/local/pgsql/pgsql-18/
#########################################################################
# Assemble everything into the final container.
#########################################################################
FROM python:3-alpine
# Install runtime dependencies
RUN apk update && apk upgrade && \
apk add \
bash \
postfix \
krb5-libs \
libcurl \
libjpeg-turbo \
shadow \
sudo \
tzdata \
libedit \
libldap \
libcap \
su-exec && \
rm -rf /var/cache/apk/*
# Copy in the Python packages
COPY --from=env-builder /venv /venv
# Copy in the tools
COPY --from=tool-builder /usr/local/pgsql /usr/local/
COPY --from=pg18-builder /usr/local/lib/libpq.so.5.18 /usr/local/lib/libpq-oauth-18.so /usr/lib/liblz4.so.1.10.0 /usr/lib/
RUN ln -s libpq.so.5.18 /usr/lib/libpq.so.5 && \
ln -s libpq.so.5.18 /usr/lib/libpq.so && \
ln -s liblz4.so.1.10.0 /usr/lib/liblz4.so.1
WORKDIR /pgadmin4
ENV PYTHONPATH=/pgadmin4
# Copy in the code and docs
COPY --from=app-builder /pgadmin4/web /pgadmin4
COPY --from=docs-builder /pgadmin4/docs/en_US/_build/html/ /pgadmin4/docs
COPY pkg/docker/run_pgadmin.py pkg/docker/gunicorn_config.py /pgadmin4/
COPY pkg/docker/entrypoint.sh /entrypoint.sh
# License files
COPY LICENSE /pgadmin4/LICENSE
# Configure everything in one RUN step
RUN /venv/bin/python3 -m pip install --no-cache-dir gunicorn==23.0.0 && \
find / -type d -name '__pycache__' -exec rm -rf {} + && \
useradd -r -u 5050 -g root -s /sbin/nologin pgadmin && \
mkdir -p /run/pgadmin /var/lib/pgadmin && \
chown pgadmin:root /run/pgadmin /var/lib/pgadmin && \
chmod g=u /run/pgadmin /var/lib/pgadmin && \
touch /pgadmin4/config_distro.py && \
chown pgadmin:root /pgadmin4/config_distro.py && \
chmod g=u /pgadmin4/config_distro.py && \
chmod g=u /etc/passwd && \
PYBIN="$(ls /usr/local/bin/python3.[0-9][0-9] 2>/dev/null | head -n1)" && \
cp "$PYBIN" /usr/local/bin/python3-cap && \
setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/python3-cap && \
ln -s /usr/local/bin/python3-cap /venv/bin/python3-cap && \
echo "pgadmin ALL = NOPASSWD: /usr/sbin/postfix start" > /etc/sudoers.d/postfix && \
echo "pgadminr ALL = NOPASSWD: /usr/sbin/postfix start" >> /etc/sudoers.d/postfix
USER 5050
# Finish up
VOLUME /var/lib/pgadmin
EXPOSE 80 443
ENTRYPOINT ["/entrypoint.sh"]