-
Notifications
You must be signed in to change notification settings - Fork 27
Expand file tree
/
Copy pathDockerfile
More file actions
341 lines (276 loc) · 13.3 KB
/
Copy pathDockerfile
File metadata and controls
341 lines (276 loc) · 13.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# check=skip=InvalidDefaultArgInFrom
# Netresearch TimeTracker - Dockerfile
#
# Build logic only - all versions defined in docker-bake.hcl (single source
# of truth — hence the deliberately default-less ARGs and the lint skip above).
# IMPORTANT: Always build with `docker bake`, never `docker build` directly
#
# Usage:
# docker bake # Build production image (app)
# docker bake app-dev # Build development image
# docker bake app-tools # Build tools image (CI/pre-commit)
# docker bake all # Build all images
# docker bake --print # Show build configuration
# =============================================================================
# ARGS - Values provided by docker-bake.hcl (no defaults here!)
# =============================================================================
ARG PHP_BASE_IMAGE
ARG NODE_VERSION
ARG COMPOSER_IMAGE
# =============================================================================
# COMPOSER - Stage to copy composer binary from
# =============================================================================
FROM ${COMPOSER_IMAGE} AS composer
# =============================================================================
# BASE - Runtime with PHP extensions
# =============================================================================
FROM ${PHP_BASE_IMAGE} AS base
# Install system dependencies and PHP extensions in single layer
RUN set -ex \
&& apt-get update \
&& apt-get upgrade -y \
&& apt-get install -y --no-install-recommends \
libzip-dev \
libpng-dev \
libldap2-dev \
libjpeg62-turbo-dev \
libfreetype6-dev \
libicu-dev \
procps \
unzip \
zlib1g-dev \
&& docker-php-ext-configure gd --with-jpeg --with-freetype \
&& docker-php-ext-configure ldap --with-libdir=lib/x86_64-linux-gnu/ \
&& docker-php-ext-install \
pdo_mysql \
ldap \
zip \
gd \
intl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
&& rm -rf /usr/share/doc/* /usr/share/man/*
# Install APCu (pinned; backs the Symfony app cache — see config/packages/cache.yaml)
ARG APCU_VERSION
RUN pecl install apcu-${APCU_VERSION} \
&& docker-php-ext-enable apcu
COPY docker/php/apcu.ini /usr/local/etc/php/conf.d/
# Worker pool sizing for the parallel page-load burst (see the file's comment).
COPY docker/php/fpm-pool.conf /usr/local/etc/php-fpm.d/zz-pool.conf
# Create non-root user
RUN addgroup --gid 1000 app \
&& adduser --uid 1000 --gid 1000 --disabled-password --gecos "" app
WORKDIR /var/www/html
# =============================================================================
# DEPS - Install dependencies (optimized for caching)
# =============================================================================
FROM base AS deps
# Get composer from official image
COPY --from=composer /usr/bin/composer /usr/bin/composer
# Install Node.js
ARG NODE_VERSION
RUN set -ex \
&& apt-get update \
&& apt-get install -y --no-install-recommends curl ca-certificates \
&& curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash - \
&& apt-get install -y --no-install-recommends nodejs \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Bun is the package manager of the new SolidJS frontend (frontend/)
COPY --from=oven/bun:1.3.14 /usr/local/bin/bun /usr/local/bin/bun
# Copy dependency manifests first (better cache). Root npm deps are just
# Playwright + axe for e2e.
COPY --chown=app:app package.json package-lock.json ./
RUN npm ci
# Root-owned and read-only for the runtime user (docker:S6504); the deps
# stage builds as root, so bun needs no ownership change here.
COPY frontend/package.json frontend/bun.lock ./frontend/
RUN bun install --cwd frontend --frozen-lockfile
COPY --chown=app:app composer.json composer.lock symfony.lock ./
# --ignore-platform-req=php needed until laminas-ldap adds PHP 8.5 support
# See: https://github.com/laminas/laminas-ldap/issues/62
RUN composer install --no-dev --no-scripts --no-autoloader --ignore-platform-req=php
# Copy application code
COPY --chown=app:app . .
# Finish composer install (autoloader, scripts)
# Set APP_ENV=prod to prevent loading dev-only bundles during cache warmup
# (MakerBundle etc. are not installed with --no-dev but bundles.php tries to load them in dev mode)
ENV CAPTAINHOOK_DISABLE=true
ENV APP_ENV=prod
RUN composer dump-autoload --optimize --classmap-authoritative \
&& composer run-script post-install-cmd --no-interaction || true
# Build the SolidJS UI (Vite).
RUN bun run --cwd frontend build
# Create var directories
RUN mkdir -p var/log var/cache \
&& chown -R app:app var/
# =============================================================================
# TOOLS - Lightweight image for CI/static analysis (no DB needed)
# =============================================================================
FROM deps AS tools
# Install dev dependencies for static analysis
RUN composer install --ignore-platform-req=php
USER app
# =============================================================================
# DEVTOOLS - Code-INDEPENDENT dev/e2e tooling, cached across code-only commits.
#
# Built FROM base (NOT deps), so none of these installs — git/curl, Xdebug,
# Symfony CLI, Node, Bun, composer, the root Playwright npm deps, and the
# Chromium browser — sit above the application code copy. A src-only change
# invalidates `deps` (its trailing `COPY . .`) but leaves this whole stage
# cached, so the ~68s of apt/pecl/chromium/composer no longer re-runs per commit.
#
# Xdebug and Chromium live ONLY here (→ dev → e2e); production (FROM base),
# profiling and tools (FROM deps) never inherit from this stage, so they stay
# free of Xdebug (would skew profiling timings) and Chromium (bloat).
# =============================================================================
FROM base AS devtools
ARG NODE_VERSION
ARG XDEBUG_VERSION
# Reproduce the env `dev`'s composer install ran under when it was FROM deps:
# CAPTAINHOOK_DISABLE skips the captainhook git-hook install (no .git in image),
# APP_ENV=prod keeps the post-install cache:clear identical to before. `dev`
# overrides APP_ENV back to `dev` at the end of its stage (as it always did).
ENV CAPTAINHOOK_DISABLE=true
ENV APP_ENV=prod
# Get composer from official image
COPY --from=composer /usr/bin/composer /usr/bin/composer
# Install Node.js (Playwright/chromium need it; also handy in the dev shell)
RUN set -ex \
&& apt-get update \
&& apt-get install -y --no-install-recommends curl ca-certificates \
&& curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash - \
&& apt-get install -y --no-install-recommends nodejs \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Bun is the package manager of the new SolidJS frontend (frontend/)
COPY --from=oven/bun:1.3.14 /usr/local/bin/bun /usr/local/bin/bun
# Install dev tools
RUN set -ex \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
git \
curl \
bash-completion \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Install Xdebug (debugging and coverage driver; enable coverage via XDEBUG_MODE=coverage)
RUN pecl install xdebug-${XDEBUG_VERSION} \
&& docker-php-ext-enable xdebug
COPY docker/php/xdebug.ini /usr/local/etc/php/conf.d/
# Install Symfony CLI
RUN curl -sS https://get.symfony.com/cli/installer | bash \
&& mv /root/.symfony*/bin/symfony /usr/local/bin/symfony \
&& mkdir -p /etc/bash_completion.d \
&& symfony completion bash > /etc/bash_completion.d/symfony \
&& echo 'source /etc/bash_completion.d/symfony' >> /etc/bash.bashrc
# Root npm deps (Playwright + axe), the chromium install, and cleanup in ONE
# layer: dev/e2e receive node_modules + manifests from deps' `COPY --from=deps`,
# so devtools' copy is transient and must not be committed (image bloat). The
# chromium browser installs to ~/.cache/ms-playwright (outside node_modules), so
# it survives the cleanup. Kept LAST of the manifest-independent installs so a
# package.json bump doesn't invalidate the apt/pecl layers above.
COPY --chown=app:app package.json package-lock.json ./
RUN npm ci \
&& npx playwright install chromium --with-deps \
&& rm -rf node_modules package.json package-lock.json
# =============================================================================
# DEV - Development image with debugging tools
# =============================================================================
FROM devtools AS dev
# Bring in the built application tree (vendor, built SolidJS UI, app code) from
# deps. This is the ONLY code-dependent layer of the dev image — it invalidates
# on any src change, but that is unavoidable and cheap (a plain copy).
COPY --from=deps --chown=app:app /var/www/html /var/www/html
# Install dev dependencies
RUN composer install --ignore-platform-req=php
RUN git config --global --add safe.directory '*'
# Dev runs as root for convenience (volume permissions)
ENV APP_ENV=dev
ENV APP_DEBUG=1
# =============================================================================
# E2E - Development image with Playwright and browsers pre-installed
# =============================================================================
FROM dev AS e2e
# Chromium + Playwright are already installed in the cached `devtools` stage.
ENV APP_ENV=test
# =============================================================================
# PRODUCTION - Minimal secure image
# =============================================================================
FROM base AS production
# Copy only what's needed from deps stage. Application code and assets are
# root-owned and read-only for the runtime user (docker:S6504); only var/
# must stay writable (cache, logs).
COPY --from=deps /var/www/html/vendor /var/www/html/vendor
COPY --from=deps /var/www/html/public /var/www/html/public
COPY --from=deps /var/www/html/config /var/www/html/config
COPY --from=deps /var/www/html/bin /var/www/html/bin
COPY --from=deps /var/www/html/src /var/www/html/src
COPY --from=deps /var/www/html/templates /var/www/html/templates
COPY --from=deps /var/www/html/translations /var/www/html/translations
COPY --from=deps /var/www/html/migrations /var/www/html/migrations
COPY --from=deps /var/www/html/sql /var/www/html/sql
COPY --from=deps --chown=app:app /var/www/html/var /var/www/html/var
# Production PHP hardening (no arg values in exception traces, no error output).
COPY docker/php/production.ini /usr/local/etc/php/conf.d/zz-production.ini
# Copy healthcheck script
COPY --chmod=755 docker/php/healthcheck.sh /usr/local/bin/healthcheck
# Copy the production entrypoint — applies pending DB migrations on start so a
# new image deployed over an existing database self-migrates (AUTO_MIGRATE=0 opts out)
COPY --chmod=755 docker/php/docker-entrypoint.sh /usr/local/bin/app-entrypoint
# Update CA certificates during build (requires root, done before USER switch)
RUN update-ca-certificates 2>/dev/null || true
# Symfony's Dotenv is booted unconditionally by bin/console and public/index.php
# and throws if the file is missing; runtime configuration comes from real
# environment variables (compose.yml), so an empty file is correct here.
RUN touch /var/www/html/.env
ENV APP_ENV=prod
ENV APP_DEBUG=0
# Build provenance, surfaced read-only on /ui/admin/status. Passed by CI (docker
# bake) from the git metadata; empty on a plain local build.
ARG APP_BUILD_REVISION=""
ARG APP_BUILD_REF=""
ARG APP_BUILD_DATE=""
ENV APP_BUILD_REVISION=${APP_BUILD_REVISION}
ENV APP_BUILD_REF=${APP_BUILD_REF}
ENV APP_BUILD_DATE=${APP_BUILD_DATE}
# Run as non-root user
USER app
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD /usr/local/bin/healthcheck
EXPOSE 9000
ENTRYPOINT ["/usr/local/bin/app-entrypoint"]
CMD ["php-fpm"]
# =============================================================================
# PROFILING - Prod-like image WITH the Symfony profiler (admin-gated).
# Never the default deployment: an operator switches the server to :profiling
# on demand to capture production profiling data, then switches back. Built on
# `deps` (no Xdebug — Xdebug would skew the very timings we measure).
# =============================================================================
FROM deps AS profiling
ENV CAPTAINHOOK_DISABLE=true
ENV APP_ENV=profiling
ENV APP_DEBUG=0
# Add dev dependencies (web-profiler-bundle, debug-bundle, stopwatch) on top of
# the prod vendor tree, keep an optimized authoritative autoloader, and warm the
# profiling cache (APCu is built into the base image).
RUN composer install --ignore-platform-req=php --no-scripts \
&& composer dump-autoload --optimize --classmap-authoritative \
&& php bin/console cache:clear --no-debug \
&& php bin/console cache:warmup --no-debug \
&& chown -R app:app var/
COPY --chmod=755 docker/php/healthcheck.sh /usr/local/bin/healthcheck
COPY --chmod=755 docker/php/docker-entrypoint.sh /usr/local/bin/app-entrypoint
RUN update-ca-certificates 2>/dev/null || true
ARG APP_BUILD_REVISION=""
ARG APP_BUILD_REF=""
ARG APP_BUILD_DATE=""
ENV APP_BUILD_REVISION=${APP_BUILD_REVISION}
ENV APP_BUILD_REF=${APP_BUILD_REF}
ENV APP_BUILD_DATE=${APP_BUILD_DATE}
USER app
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD /usr/local/bin/healthcheck
EXPOSE 9000
ENTRYPOINT ["/usr/local/bin/app-entrypoint"]
CMD ["php-fpm"]