From 759c22afafb39092546022a0911dfa811fb7bf62 Mon Sep 17 00:00:00 2001 From: David Dyer Date: Sun, 25 Oct 2026 17:09:05 -0400 Subject: [PATCH] Add Dockerfile and .dockerignore for improved containerization and security --- .dockerignore | 17 +++++++++++++++++ Dockerfile | 20 ++++++++++++++++++++ docker-compose.yaml | 36 +++++++++++++++--------------------- 3 files changed, 52 insertions(+), 21 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..658e144 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +# Secrets and runtime state — never in the build context. +config.php +db/fitz.sqlite* +cache/ + +# Rebuilt inside the image. +node_modules/ +dist/ +styles.css +styles.css.map + +# Dev-only. +.git/ +.claude/ +.DS_Store +.hintrc +dev.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..74a449c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +# syntax=docker/dockerfile:1 + +# Build stage: compile Sass and assemble the allowlisted dist/ folder. +# config.php never enters the image (.dockerignore), and build-dist.js +# doesn't copy it anyway. +FROM node:24-alpine AS build +WORKDIR /src +COPY package.json package-lock.json ./ +RUN npm ci +COPY . . +RUN npm run dist + +# Runtime stage. Production php.ini and expose_php = Off are baked in here, +# replacing the startup `command:` in docker-compose.yaml. Files are owned +# by root with 644/755 (normalized by build-dist.js), so www-data can read +# the site but not modify it; data/ and config/ arrive as mounts. +FROM php:8.5-apache +RUN cp "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \ + && printf 'expose_php = Off\n' > "$PHP_INI_DIR/conf.d/zz-hardening.ini" +COPY --from=build /src/dist/ /var/www/html/ diff --git a/docker-compose.yaml b/docker-compose.yaml index 6be7add..442efab 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,12 +1,15 @@ -# The Old Fitz — production compose for the VPS. +# The Old Fitz — compose for any Docker host (test or production). # -# Expected layout, one folder per site: +# The site itself is baked into the image (see Dockerfile); the host only +# holds state. Expected layout, one folder per site: # theoldfitz/ # docker-compose.yaml (this file) -# .env (optional, see PROXY_NETWORK below) -# www/ (contents of `npm run dist`) -# config/config.php (live secrets — never inside www/, never in dist/) -# data/ (fitz.sqlite + cache/ — never inside www/) +# .env (FITZ_IMAGE, and PROXY_NETWORK if not "proxy") +# config/config.php (live secrets — never in the image) +# data/ (fitz.sqlite + cache/ — never in the image) +# +# Deploy: `docker compose pull && docker compose up -d`. +# Manual fallback from a checkout of the repo: `docker compose up -d --build`. # # First run: create data/ and `chown 33:33 data` (www-data in the image). # Copy config.example.php to config/config.php, fill it in, and make it @@ -27,27 +30,18 @@ networks: services: the-old-fitz: - image: php:8.5-apache + # Servers set FITZ_IMAGE in .env to the registry image, e.g. + # gitea.example.com/david/the-old-fitz:latest. Unset, it falls back to a + # local tag so `up --build` works from a checkout. + image: ${FITZ_IMAGE:-the-old-fitz:local} + build: . container_name: the-old-fitz restart: unless-stopped - # The image ships without a php.ini, so PHP falls back to dev-style - # defaults and shows errors (with server paths) to visitors. Activate - # the image's bundled production ini — display_errors off, errors to - # the log — and hide the PHP version header, then start Apache as usual. - # Paths are spelled out (the image's $PHP_INI_DIR) rather than using - # the variable, so no $-escaping is needed. - command: - - bash - - -c - - cp /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini && echo 'expose_php = Off' > /usr/local/etc/php/conf.d/zz-hardening.ini && exec apache2-foreground networks: - proxy volumes: - - type: bind - source: ./www - target: /var/www/html # Database and cache live outside the web root so Apache can't serve - # them and deploys to www/ never touch them. + # them, and outside the image so deploys never touch them. - type: bind source: ./data target: /var/www/data