Dockerfile
Rama main · réplica de lectura del repositorio original.
# syntax=docker/dockerfile:1.28
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 Netresearch DTT GmbH
# GLPI php-fpm image — only PHP + the GLPI application code.
# Web serving (nginx), scheduling (ofelia), and DB (mariadb) live in
# separate containers in compose.yml. This image is single-purpose.
#
# Two stages:
# 1. fetch — downloads + verifies the bundled GLPI release tarball
# 2. runtime — minimal php-fpm with the app code, runs as www-data
#
# GLPI ships a *bundled* release tarball (glpi-<version>.tgz) with vendor/
# already installed, so — unlike a Composer-based app — there is NO composer
# install step and no build-time GitHub rate-limit to manage. That makes this
# image both simpler and more reproducible than its Snipe-IT sibling.
#
# Build args:
# PHP_VERSION — base PHP version (default 8.5; GLPI 12.0.x supports
# PHP 8.3-8.5, and its CI tests 8.3 and 8.5.)
# ALPINE_VERSION — Alpine tag for the php images (default 3.24)
# GLPI_VERSION — GLPI release (default 12.0.0 — keep in sync with .glpi-version)
# GLPI_SHA256 — sha256 of glpi-${GLPI_VERSION}.tgz (supply-chain pin; "" skips)
# renovate: datasource=docker depName=php versioning=docker
ARG PHP_VERSION=8.5
# renovate: datasource=docker depName=alpine versioning=docker
ARG ALPINE_VERSION=3.24
# =====================================================================
# Stage 1: fetch — download + verify the bundled GLPI release tarball
# =====================================================================
FROM alpine:${ALPINE_VERSION} AS fetch
# pipefail — surface errors in piped curl downloads (hadolint DL4006)
SHELL ["/bin/ash", "-o", "pipefail", "-c"]
ARG GLPI_VERSION=12.0.0
# Optional integrity pin. When set, the download is rejected unless its
# sha256 matches — closes a supply-chain gap (a swapped release asset can't
# slip through). Left empty by default so a bare `docker build` works; CI
# (build.yml) passes the value resolved from the release at build time.
ARG GLPI_SHA256=""
RUN set -eux; \
apk add --no-cache curl ca-certificates tar; \
curl -fsSL -o /tmp/glpi.tgz \
"https://github.com/glpi-project/glpi/releases/download/${GLPI_VERSION}/glpi-${GLPI_VERSION}.tgz"; \
if [ -n "${GLPI_SHA256}" ]; then \
echo "${GLPI_SHA256} /tmp/glpi.tgz" | sha256sum -c -; \
else \
echo "[fetch] GLPI_SHA256 not provided — skipping integrity check (set it in CI)"; \
fi; \
mkdir -p /opt/glpi; \
# The tarball's top-level dir is `glpi/` — strip it so the app root is /opt/glpi.
tar xzf /tmp/glpi.tgz -C /opt/glpi --strip-components=1; \
rm -f /tmp/glpi.tgz; \
# Sanity: the bundled dist must carry vendor/, the public/ docroot and the CLI.
test -f /opt/glpi/public/index.php; \
test -f /opt/glpi/bin/console; \
test -d /opt/glpi/vendor; \
# The release tarball ships an empty `files/` and `config/` under the code
# root. We relocate those to volumes (GLPI_VAR_DIR / GLPI_CONFIG_DIR), so
# drop the in-tree copies to keep the code layer read-only and clean.
rm -rf /opt/glpi/files /opt/glpi/config; \
# Record the version for ops introspection.
echo "${GLPI_VERSION}" > /opt/glpi/VERSION
# =====================================================================
# Stage 2: runtime — php-fpm only
# =====================================================================
FROM php:${PHP_VERSION}-fpm-alpine${ALPINE_VERSION} AS runtime
# pipefail — surface errors in piped downloads (hadolint DL4006)
SHELL ["/bin/ash", "-o", "pipefail", "-c"]
ARG GLPI_VERSION=12.0.0
ARG PHP_VERSION=8.5
ARG BUILD_DATE
ARG VCS_REF
LABEL org.opencontainers.image.title="glpi-php-fpm" \
org.opencontainers.image.description="GLPI ${GLPI_VERSION} on PHP ${PHP_VERSION} / Alpine — php-fpm only (use with glpi-docker-compose-stack)" \
org.opencontainers.image.url="https://github.com/netresearch/glpi-docker-compose-stack" \
org.opencontainers.image.source="https://github.com/netresearch/glpi-docker-compose-stack" \
org.opencontainers.image.documentation="https://github.com/netresearch/glpi-docker-compose-stack#readme" \
org.opencontainers.image.vendor="Netresearch DTT GmbH" \
org.opencontainers.image.licenses="GPL-3.0-or-later" \
org.opencontainers.image.version="${GLPI_VERSION}" \
org.opencontainers.image.created="${BUILD_DATE}" \
org.opencontainers.image.revision="${VCS_REF}"
# GLPI reads these to locate its mutable state OUTSIDE the (read-only) code
# root, so config/files/plugins/logs all live on named volumes:
# GLPI_CONFIG_DIR — config_db.php + glpicrypt.key + local config
# GLPI_VAR_DIR — files/ tree (cache, sessions, uploads, pictures, …)
# GLPI_MARKETPLACE_DIR — marketplace-installed plugins
# GLPI_LOG_DIR — application logs
# Using env vars (not inc/downstream.php) is the reliable path on GLPI 11
# (downstream-based relocation has open bugs — glpi-project/glpi#23103).
ENV GLPI_CONFIG_DIR=/etc/glpi \
GLPI_VAR_DIR=/var/lib/glpi/files \
GLPI_MARKETPLACE_DIR=/var/lib/glpi/plugins \
GLPI_LOG_DIR=/var/log/glpi
# Runtime libraries (no -dev) the extensions link against, plus the small
# operational toolset the entrypoint/healthcheck need (tini for signal
# handling, su-exec for privilege drop, fcgi for the cgi-fcgi healthcheck).
RUN --mount=type=bind,from=ghcr.io/php/pie:1.5.2-bin@sha256:a254c78b51ca34daef0541fa1cf0147385c93ba5ca799868392275d947f00fe7,source=/pie,target=/usr/local/bin/pie \
set -eux; \
apk add --no-cache \
bash ca-certificates curl tini su-exec fcgi tzdata \
icu-libs libpng libjpeg-turbo freetype libzip \
oniguruma libldap libsodium libbz2 libxml2 \
# Build deps in a virtual package so they can be dropped in the same layer.
&& apk add --no-cache --virtual .build-deps \
$PHPIZE_DEPS \
icu-dev libpng-dev libjpeg-turbo-dev freetype-dev libzip-dev \
oniguruma-dev openldap-dev libsodium-dev bzip2-dev \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j"$(nproc)" \
bcmath bz2 exif gd intl ldap mbstring mysqli sodium zip \
# PHP 8.5 compiles OPcache into the core, so it is built as an extension
# only on an older PHP_VERSION (docker-php-ext-install fails on 8.5).
&& { php -m | grep -qix 'zend opcache' || docker-php-ext-install opcache; } \
# phpredis — required for GLPI's redis/valkey cache backend
# (cache:configure refuses a redis:// DSN without it). Pinned for
# reproducible builds; bump deliberately. Installed with PIE from Packagist,
# pecl.php.net is deprecated and its downloads fail.
&& pie install --no-interaction phpredis/phpredis:6.3.0 \
&& docker-php-ext-enable redis \
&& apk del .build-deps \
&& rm -rf /tmp/* /var/cache/apk/* /usr/src/php* \
/usr/local/lib/php/test /usr/local/lib/php/doc \
# Fail the build loudly if any required extension is missing. `php -m`
# lists OPcache as "Zend OPcache", so it is checked separately below
# rather than as a bare "opcache" line.
&& php -m | tr '[:upper:]' '[:lower:]' | sort > /tmp/mods \
&& for ext in bcmath ctype curl dom exif fileinfo filter gd iconv intl \
ldap mbstring mysqli openssl redis session simplexml sodium \
tokenizer zip zlib; do \
grep -qx "$ext" /tmp/mods || { echo "MISSING ext: $ext" >&2; exit 1; }; \
done \
&& grep -qx "zend opcache" /tmp/mods || { echo "MISSING ext: opcache" >&2; exit 1; } \
&& rm -f /tmp/mods
WORKDIR /var/www/glpi
# Defense-in-depth: application code is owned by root and only readable (not
# writable) by the www-data group. A compromised php-fpm worker (www-data)
# cannot modify GLPI's PHP source, vendor/ or public/ assets at runtime.
# The paths GLPI legitimately writes to (GLPI_CONFIG_DIR / GLPI_VAR_DIR /
# GLPI_MARKETPLACE_DIR / GLPI_LOG_DIR and /run/php-fpm) are chown'd www-data
# below — and again by entrypoint.sh, to survive fresh volume mounts.
COPY --from=fetch --chown=root:www-data /opt/glpi /var/www/glpi
COPY rootfs/ /
# All runtime-stage filesystem setup folded into one layer:
# 1. create the writable state dirs (relocated off the code root),
# 2. chown them + the php-fpm socket dir to www-data,
# 3. mark the entrypoint executable.
# Each is re-applied by entrypoint.sh at container start so it works whether
# the operator mounts named volumes (image chown survives until first write),
# bind-mounts (host UID wins — entrypoint fixes it) or tmpfs (mount masks the
# image chown — entrypoint fixes it).
RUN set -eux; \
mkdir -p /etc/glpi /var/lib/glpi/files /var/lib/glpi/plugins \
/var/log/glpi /run/php-fpm \
&& chown -R www-data:www-data \
/etc/glpi /var/lib/glpi /var/log/glpi /run/php-fpm \
&& chmod 0755 /usr/local/bin/entrypoint.sh
# --start-interval=5s probes every 5s during the start_period instead of
# waiting the full --interval, so `docker compose up --wait` returns as soon
# as php-fpm accepts FastCGI rather than 30s+ later. The probe pings php-fpm's
# own /ping endpoint (configured in php-fpm.d/zz-glpi.conf) over the unix
# socket — it proves the worker pool is alive, independent of DB/app state.
HEALTHCHECK --interval=30s --timeout=10s --start-period=120s --start-interval=5s --retries=3 \
CMD SCRIPT_NAME=/ping SCRIPT_FILENAME=/ping REQUEST_METHOD=GET \
cgi-fcgi -bind -connect /run/php-fpm/glpi.sock 2>/dev/null \
| grep -q "pong" || exit 1
# Graceful php-fpm shutdown: tini forwards the orchestrator's SIGTERM, but
# php-fpm reads SIGTERM as "fast shutdown" (kills in-flight requests). SIGQUIT
# is php-fpm's graceful drain — finish active requests, then exit. Critical
# during rolling updates so users mid-request don't see 502s.
STOPSIGNAL SIGQUIT
ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/entrypoint.sh"]
CMD ["php-fpm", "--nodaemonize"]