Dockerized PHP/Apache deployment for the Daily Dilbert viewer.
At container startup, the entrypoint checks for comics in /var/www/html/comics.
If the folder is missing or empty, it downloads and extracts:
https://cds.xocloud.nl/mta1ujxiq8ln1rl/dilbert-comics.tgz
- Apache + PHP runtime image (
php:8.3-apache) - Startup bootstrap script that downloads/extracts the comics archive
- Docker Compose service for one-command deploys
- Responsive comic viewer UI optimized for mobile, including stacked touch-friendly controls and improved landscape-phone behavior
- Docker Engine 24+ (or compatible)
- Docker Compose v2 (
docker compose) - Network access from the container to the comics archive URL
From this folder:
docker compose up -d --buildThen open:
http://localhost
Stop and remove container/network:
docker compose downBuild image:
docker build -t daily-dilbert:v1.1.0 .Run container:
docker run -d --restart unless-stopped -p 80:80 --name daily-dilbert daily-dilbert:v1.1.0If your daemon is reachable over SSH:
export DOCKER_HOST=ssh://user@remote-host
docker compose up -d --buildOr with plain Docker:
export DOCKER_HOST=ssh://user@remote-host
docker build -t daily-dilbert:v1.1.0 .
docker run -d --restart unless-stopped -p 80:80 --name daily-dilbert daily-dilbert:v1.1.0Environment variables supported by the container:
COMICS_ARCHIVE_URL(default:https://cds.xocloud.nl/mta1ujxiq8ln1rl/dilbert-comics.tgz)COMICS_ARCHIVE_SHA256(default:521749868533259f1a5d78baa594202bc8362cf9b60b35c0d315100f4a41a87e)WEB_ROOT(default:/var/www/html)
Example:
docker run -d --restart unless-stopped -p 80:80 \
-e COMICS_ARCHIVE_URL=https://example.org/dilbert-comics.tgz \
-e COMICS_ARCHIVE_SHA256=<sha256-of-archive> \
--name daily-dilbert daily-dilbert:v1.1.0If you change COMICS_ARCHIVE_URL, always set the matching COMICS_ARCHIVE_SHA256 value. The container now refuses to extract archives when checksum verification fails.
For native clients that only need the comic image (no web viewer rendering), use:
GET /api/current.phpto fetch the latest comic (no query params required)GET /api/comic.php?date=YYYY-MM-DDto fetch a comic for a specific dateGET /api/comic.php?latest=1to fetch the most recent comic in the archive- add
&download=1to force attachment download behavior
Examples:
curl -L -o latest.gif "http://localhost/api/current.php"
curl -o comic.gif "http://localhost/api/comic.php?date=1989-04-16"
curl -L -o latest.gif "http://localhost/api/comic.php?latest=1"The image enables Apache response caching headers:
- Comic images (
*.gif):Cache-Control: public, max-age=31536000, immutable - Dynamic pages (
*.php,*.html):Cache-Control: no-cache, private, must-revalidate
This policy is defined in apache-cache.conf and enabled during image build.
Comics are downloaded only when /var/www/html/comics is missing or empty.
To force a fresh download:
- Remove the running container.
- Start a new container from the same image.
If you use persistent volumes for /var/www/html/comics, clear that volume content first.
Show logs:
docker logs -f daily-dilbertRestart service (Compose):
docker compose restartRebuild after code changes:
docker compose up -d --buildpermission denied /var/run/docker.sock: run commands with a user that has Docker daemon access.- comics not showing: check container logs and verify outbound access to
cds.xocloud.nl. - port already in use: change host binding, for example
-p 8080:80.
The viewer is responsive and optimized for touch devices.
- Search and navigation controls stack into full-width rows on narrow viewports.
- Buttons and input fields use larger touch targets for easier tapping.
- Comic images scale to screen width and keep aspect ratio.
- Extra layout tuning is applied for short-height landscape phone screens.
- On mobile, comics can split into swipeable panels using detected white vertical separators between strips.
- If separator detection does not find a reliable split, the viewer automatically falls back to the full comic image.
Use browser device emulation or a real phone and validate:
- Portrait ~390×844 (modern phone): search field + button are easy to tap.
- Portrait ~320×568 (small phone): no horizontal scroll, controls remain readable.
- Landscape with low height (for example ~844×390): title/search/nav remain visible without crowding.
- Navigation buttons (
Previous/Next) remain accessible after comic image loads. - Date search input opens native date picker and loads the matching comic.
- Multi-strip comic on mobile: horizontal swipe moves cleanly between detected panels.
- Comic without clear separators: full-image fallback is shown (no broken splits).
Use this checklist when deploying to production.
- Docker Engine and Compose installed and updated.
- Only required inbound ports are open:
22/tcp(SSH, restricted)80/tcpand443/tcp(web)
- Outbound HTTPS (
443/tcp) allowed so the container can download the comics archive. - Time sync enabled (
systemd-timesyncd/NTP).
-
A/AAAArecord points your domain to the server. - DNS propagated before enabling TLS.
- Put the app behind a reverse proxy (Nginx, Caddy, or Traefik).
- Enable HTTPS with a valid certificate (for example Let’s Encrypt).
- Redirect HTTP to HTTPS.
- Set
X-Forwarded-ProtoandHostheaders correctly in proxy config.
- Container is started with restart policy (
unless-stopped). - Decide persistence strategy for comics data:
- no volume: comics downloaded on each fresh container
- named/bind volume: comics cached across restarts/redeploys
- Image and deployment commands are documented in your ops runbook.
- SSH key auth only; password login disabled.
- Root login via SSH disabled.
- Host firewall enabled (
ufw/nftables). - Automatic OS security updates enabled.
- Reverse proxy adds basic security headers (
HSTS,X-Content-Type-Options,X-Frame-Options).
- Container logs are collected/rotated (
docker logs, journald, or external logging). - Basic uptime check configured (HTTP health endpoint or homepage check).
- Backups defined for any persistent volumes and proxy config.
- Restore procedure tested at least once.
-
docker compose psshows service healthy/running. - Homepage loads over HTTPS.
- Comics list loads and image navigation works.
- Restart test succeeds (
docker compose restart).
Dockerfile: image definitiondocker-entrypoint.sh: startup archive download/extract logicdocker-compose.yml: compose deployment definitionindex.php,get_comics.php: web application