Extending the Image¶
The image is built to be extended. Three patterns cover most needs: extra daemons, startup scripts, and running commands as root.
Adding extra daemons via runit¶
The runtime is managed by runit. Each daemon has its own directory under /etc/service/<name>/ containing an executable run script. runit keeps them alive and restarts them if they crash.
Example: a memcached sidecar.
Create memcached.sh:
Then in your Dockerfile:
FROM erseco/alpine-php-webserver:latest
USER root
RUN apk add --no-cache memcached \
&& mkdir -p /etc/service/memcached
COPY memcached.sh /etc/service/memcached/run
RUN chmod +x /etc/service/memcached/run
USER nobody
Important rules:
- The daemon must run in the foreground. Don't fork, don't double-fork, don't write a PID file. Use the flag your daemon provides (
-F,-f,--no-daemonize). - Exec the final command (
execin the shell script) so signals propagate correctly. - Redirect stderr to stdout (
exec 2>&1 ...) so both streams reachdocker logs. - Use
chpst -u <user>to drop privileges if the daemon doesn't do it itself.
Info
The image's own nginx and php services are defined exactly the same way under rootfs/etc/service/nginx/run and rootfs/etc/service/php/run. Read them as templates.
Startup scripts (/docker-entrypoint-init.d/)¶
Drop executable scripts into /docker-entrypoint-init.d/ and they run in lexicographic order on every container start, right before runit takes over. The booting fails if any script exits non-zero.
COPY logtime.sh /docker-entrypoint-init.d/10-logtime.sh
RUN chmod +x /docker-entrypoint-init.d/10-logtime.sh
Typical use cases:
- Waiting for a database container (
nc -z db 5432). - Warming caches.
- Expanding env-var templates into config files not already covered by
envsubst. - Running
composer dump-autoloadafter mounting code.
Naming convention: prefix with a two-digit number (10-, 20-, 90-) to control order.
Running commands as root¶
The container runs as nobody. Sometimes you need root to install packages, inspect file ownership, or debug something. Use --user root on docker compose exec:
Examples:
# Install a debugging tool
docker compose exec --user root web sh -c "apk add --no-cache nano curl htop"
# Look at who owns the mounted volume
docker compose exec --user root web ls -la /var/www/html
# Run a one-off PHP tool as root
docker compose exec --user root web php -r 'echo PHP_VERSION, PHP_EOL;'
Adding PHP extensions¶
Packaged in Alpine? One line:
FROM erseco/alpine-php-webserver:latest
USER root
RUN apk add --no-cache php84-ldap php84-pecl-redis php84-pecl-mongodb
USER nobody
Not packaged? Build from PECL with apk add --no-cache $PHPIZE_DEPS plus pecl install. This is heavier and rarely needed.
Mounting custom config at runtime¶
You don't always need a new image. For quick tweaks, mount a file directly:
# Extra PHP settings
docker run --rm -p 8080:8080 \
-v "$PWD/90-app.ini:/etc/php84/conf.d/90-app.ini:ro" \
erseco/alpine-php-webserver
# Extra Nginx server-block rules
docker run --rm -p 8080:8080 \
-v "$PWD/routes.conf:/etc/nginx/server-conf.d/routes.conf:ro" \
erseco/alpine-php-webserver
See Nginx Configuration and PHP Configuration for the directory conventions.
Rebuild vs. override: which to pick?¶
| Situation | What to do |
|---|---|
Tweaking limits (memory_limit, client_max_body_size, timezone…) |
Environment variables. No rebuild needed. |
| Adding one PHP extension | Build your own image on top with apk add. |
| Adding one Nginx route | Mount a file into /etc/nginx/server-conf.d/. |
| Adding Composer dependencies | Build your own image. See Composer & Building. |
| Running an extra daemon (queue worker, supervisor) | Build your own image with an extra runit entry. |
| Debugging production | docker compose exec --user root web sh — non-persistent. |