-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathDockerfile
More file actions
228 lines (209 loc) · 9.57 KB
/
Dockerfile
File metadata and controls
228 lines (209 loc) · 9.57 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
# =============================================================================
# ParkHub PHP — SOTA-2026 Wolfi runtime, multi-stage build
#
# Runtime stage uses Chainguard Wolfi base instead of Debian. This:
# - eliminates libc6 CVE-2026-5450 (Critical, won't-fix on Debian Bookworm)
# - drops the broader Debian package surface that grype/trivy flags
# - aligns with CLAUDE.md's "Wolfi base only for service runtime images"
#
# Frontend stage keeps node:22-slim (Debian-slim Node toolchain), but pulled
# from the internal registry mirror at 192.168.178.250:5000 — never Docker Hub
# (CLAUDE.md: NEVER pull from Docker Hub in builds — 429 risk).
#
# Vendor stage uses wolfi-base + apk-installed php-8.4 + composer + git.
# composer:2 image is no longer needed; composer ships in Wolfi.
# =============================================================================
# ---------------------------------------------------------------------------
# Stage 1: Frontend build (Astro + Vite)
# Mirrored node:22-slim — pinned to the registry digest, NOT Docker Hub.
#
# NODE_BASE / WOLFI_BASE are parameterized so cloud CI (GitHub Actions) can
# pass --build-arg NODE_BASE=docker.io/library/node:22-slim@sha256:689c1104...
# and --build-arg WOLFI_BASE=cgr.dev/chainguard/wolfi-base@sha256:4973aa3c2ccbe13fe2049aab539b0ab342ec584bd5b54a269d55d4891091c639 while
# local + gitea-runner builds default to the LAN mirror. Same images either
# way, just different ingress to them.
# ---------------------------------------------------------------------------
ARG NODE_BASE=192.168.178.250:5000/node:22-slim@sha256:689c11043dad91472750cd824c97dd5e2318e9dd6f954e492fe7af0135d33ceb
ARG WOLFI_BASE=192.168.178.250:5000/wolfi-base@sha256:4973aa3c2ccbe13fe2049aab539b0ab342ec584bd5b54a269d55d4891091c639
FROM ${NODE_BASE} AS frontend
WORKDIR /app
COPY parkhub-web/package*.json ./
RUN npm ci
COPY parkhub-web/ ./
RUN DOCKER=1 npm run build
# ---------------------------------------------------------------------------
# Stage 2: Composer dependency install (no dev deps)
# Wolfi base + apk-installed php-8.4 + composer + git — replaces composer:2
# Docker Hub image. Smaller surface, no Hub pull.
# ---------------------------------------------------------------------------
FROM ${WOLFI_BASE} AS vendor
# Vendor stage runs `composer install` + `composer dump-autoload`. The latter
# triggers Laravel's `package:discover` post-autoload-dump script, which
# touches the DB layer (PDO). Composer's Wolfi package depends on virtual PHP
# extension packages, so pin every provider to php-8.4 to avoid mixing modules
# from the default PHP stream.
# `apk upgrade` first to align with current Wolfi repo (mirrored base may
# lag); keeps vendor layer's transitive libs scan-clean too.
RUN apk update && apk upgrade --no-cache --available && apk add --no-cache \
bash \
ca-certificates \
composer \
git \
php-8.4 \
php-8.4-ctype \
php-8.4-curl \
php-8.4-dom \
php-8.4-fileinfo \
php-8.4-iconv \
php-8.4-mbstring \
php-8.4-openssl \
php-8.4-pdo \
php-8.4-pdo_sqlite \
php-8.4-phar \
php-8.4-simplexml \
php-8.4-xml \
php-8.4-zip
WORKDIR /app
COPY composer.json composer.lock ./
# `--no-scripts` here too — keep all Laravel post-install scripts off until
# runtime where the full app + DB env is present. Also `--ignore-platform-reqs`
# defensively so any extension introduced by a transient upstream package
# update doesn't break the build (vendor stage doesn't ship to runtime).
RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist --no-interaction
# Copy full app for autoload generation.
COPY . .
RUN composer dump-autoload --optimize --no-dev --no-scripts
# ---------------------------------------------------------------------------
# Stage 3: Runtime — Wolfi + apache2 + php-8.4-apache + extensions
# Replaces docker.io/library/php:8.4-apache (Debian Bookworm). Closes
# libc6 CVE-2026-5450 because Wolfi tracks current upstream and is
# scanned daily by Chainguard.
# ---------------------------------------------------------------------------
FROM ${WOLFI_BASE} AS runtime
# Single apk layer. `apk upgrade --no-cache` first to pull current glibc/etc.
# (mirrored wolfi-base digest can lag the apk repo by a release; without
# upgrade, grype flags glibc 2.43-r6 → 2.43-r7 CVE-2026-5450/-5928).
RUN apk update && apk upgrade --no-cache --available && apk add --no-cache \
apache2 \
apache2-config \
apache2-utils \
bash \
ca-certificates \
gosu \
php-8.4 \
php-8.4-apache \
php-8.4-bcmath \
php-8.4-ctype \
php-8.4-curl \
php-8.4-dom \
php-8.4-fileinfo \
php-8.4-gd \
php-8.4-iconv \
php-8.4-mbstring \
php-8.4-opcache \
php-8.4-openssl \
php-8.4-pdo \
php-8.4-mysqlnd \
php-8.4-pdo_mysql \
php-8.4-pdo_pgsql \
php-8.4-pdo_sqlite \
php-8.4-pgsql \
php-8.4-phar \
php-8.4-simplexml \
php-8.4-xml \
php-8.4-zip \
sqlite-libs \
tini \
wget
# Drop-in compat with existing entrypoint (which expects www-data:33).
# Wolfi defaults to apache:apache; map www-data to UID 33 explicitly to
# match historical Debian image + entrypoint chown commands.
RUN if ! getent group www-data >/dev/null 2>&1; then addgroup -g 33 -S www-data; fi \
&& if ! getent passwd www-data >/dev/null 2>&1; then \
adduser -u 33 -D -S -G www-data -h /var/www -s /sbin/nologin www-data; \
fi \
&& mkdir -p /var/www/html /var/log/apache2 /var/run/apache2 \
&& chown -R www-data:www-data /var/www /var/log/apache2 /var/run/apache2
# PHP production hardening + OPcache tuning.
RUN mkdir -p /etc/php/conf.d \
&& { \
echo "expose_php = Off"; \
echo "opcache.enable=1"; \
echo "opcache.memory_consumption=128"; \
echo "opcache.interned_strings_buffer=16"; \
echo "opcache.max_accelerated_files=10000"; \
echo "opcache.validate_timestamps=0"; \
echo "opcache.jit=on"; \
echo "opcache.jit_buffer_size=64M"; \
echo "realpath_cache_size=4096K"; \
echo "realpath_cache_ttl=600"; \
} > /etc/php/conf.d/zz-production.ini
# Apache config — append app overlay; upstream httpd.conf provides MPM + base.
RUN mkdir -p /etc/apache2/conf.d \
&& { \
echo "ServerTokens Prod"; \
echo "ServerSignature Off"; \
echo "TraceEnable Off"; \
echo "Listen 10000"; \
echo "LoadModule rewrite_module /usr/lib/apache2/modules/mod_rewrite.so"; \
echo "LoadModule headers_module /usr/lib/apache2/modules/mod_headers.so"; \
echo "LoadModule deflate_module /usr/lib/apache2/modules/mod_deflate.so"; \
echo "LoadModule expires_module /usr/lib/apache2/modules/mod_expires.so"; \
echo "LoadModule mime_module /usr/lib/apache2/modules/mod_mime.so"; \
echo "LoadModule dir_module /usr/lib/apache2/modules/mod_dir.so"; \
echo "LoadModule env_module /usr/lib/apache2/modules/mod_env.so"; \
echo ""; \
echo "ServerName parkhub.local"; \
echo "DocumentRoot /var/www/html/public"; \
echo "DirectoryIndex index.php index.html"; \
echo ""; \
echo "<Directory /var/www/html/public>"; \
echo " Options FollowSymLinks"; \
echo " AllowOverride All"; \
echo " Require all granted"; \
echo "</Directory>"; \
echo ""; \
echo "ErrorLog /dev/stderr"; \
echo "CustomLog /dev/stdout combined"; \
} > /etc/apache2/conf.d/zz-parkhub.conf
# Wolfi's stock httpd.conf does not auto-include `conf.d/*.conf` (unlike
# Debian) and does not load mod_php — the php-8.4-apache package only ships
# `modules/libphp.so` plus `extra/php_module.conf`, leaving wiring to the
# operator. Without these three lines the runtime serves zero PHP: Apache
# starts cleanly, but every request to /index.php returns raw source (or
# 403/404 depending on handler order) and the /api/v1/health/live probe
# never goes green.
RUN { \
echo ""; \
echo "# parkhub wiring — load mod_php and pull in the conf.d overlay."; \
echo "LoadModule php_module /usr/lib/apache2/modules/libphp.so"; \
echo "Include /etc/apache2/extra/php_module.conf"; \
echo "Include /etc/apache2/conf.d/*.conf"; \
} >> /etc/apache2/httpd.conf
WORKDIR /var/www/html
# Copy application code (without vendor/ and node_modules/ per .dockerignore).
COPY --chown=www-data:www-data . .
# Copy composer vendor + built Astro frontend assets.
COPY --chown=www-data:www-data --from=vendor /app/vendor/ ./vendor/
COPY --chown=www-data:www-data --from=frontend /app/dist/ ./public/
# Remove installer (must not be reachable in production).
RUN rm -f /var/www/html/public/install.php
# Storage + cache permissions.
RUN chown -R www-data:www-data /var/www/html \
&& chmod -R 755 storage bootstrap/cache \
&& mkdir -p database \
&& touch database/database.sqlite \
&& chown www-data:www-data database/database.sqlite
# Entrypoint.
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
# Environment.
ENV APP_ENV=production
ENV PORT=10000
EXPOSE 10000
# Health check.
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=5 \
CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:${PORT}/api/v1/health/live || exit 1
# tini reaps zombies + signals; entrypoint handles env wiring; httpd serves.
ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/docker-entrypoint.sh"]
CMD ["httpd", "-DFOREGROUND"]