harden(docker): non-root, dedicated healthcheck, opcache split, graceful shutdown

Coolify-doc-driven production hardening of the deploy stack:
- run the whole stack as non-root www-data; nginx on 8080 (non-privileged),
  pid in /tmp, user directive dropped (Coolify routes to any port)
- docker/healthcheck.sh: hit real /health route via PHP (not just port probe)
- split OPcache config into docker/php/opcache.ini
- graceful shutdown: supervisord stopsignal/stopwaitsecs + worker stop_grace_period
- APCu intentionally not added (Symfony cache uses redis)
- DEPLOY.md: 8080 port, non-root, resource-limit guidance

Verified on linux/amd64: non-root uid=82, /health 200, migrations run,
worker process healthcheck OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-06-28 15:27:34 +03:30
co-authored by Claude Opus 4.8
parent c74b06b3cc
commit 5150365d2c
8 changed files with 84 additions and 23 deletions
+20 -3
View File
@@ -85,19 +85,36 @@ RUN [ -f .env ] || printf 'APP_ENV=prod\nAPP_DEBUG=0\n' > .env
# Container configuration.
COPY docker/php/php.ini /usr/local/etc/php/conf.d/zz-app.ini
COPY docker/php/opcache.ini /usr/local/etc/php/conf.d/zz-opcache.ini
COPY docker/php/zz-pool.conf /usr/local/etc/php-fpm.d/zz-pool.conf
COPY docker/nginx/default.conf /etc/nginx/http.d/default.conf
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh \
COPY docker/healthcheck.sh /usr/local/bin/healthcheck.sh
# Make nginx run as the non-root www-data user:
# - drop the `user nginx;` directive (ignored + warns when master isn't root)
# - put the pid file in a www-data-writable path (default /run/nginx is root-only)
RUN sed -i '/^user /d' /etc/nginx/nginx.conf \
&& sed -i '1i pid /tmp/nginx.pid;' /etc/nginx/nginx.conf
RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/healthcheck.sh \
&& mkdir -p var/cache var/log var/uploads public/uploads config/jwt \
# Source may be copied with restrictive (0600) host perms; ensure the runtime
# user (www-data) can read all app files — opcache preload runs as www-data
# and otherwise fails with "Permission denied" on /app/config/preload.php.
&& chmod -R a+rX /app \
&& chown -R www-data:www-data var public/uploads config/jwt
&& chown -R www-data:www-data var public/uploads config/jwt \
# nginx (run as www-data) needs to write its temp/cache/log dirs.
&& chown -R www-data:www-data /var/lib/nginx /var/log/nginx 2>/dev/null || true
EXPOSE 80
# Drop privileges: the entrypoint, supervisord, php-fpm and nginx all run as
# www-data. nginx binds 8080 (non-privileged) so root is never required.
USER www-data
EXPOSE 8080
HEALTHCHECK --interval=15s --timeout=5s --retries=5 --start-period=60s \
CMD ["/usr/local/bin/healthcheck.sh"]
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]