Table of Contents
Here’s a pattern I use in nearly every Docker project: create scripts at build time, execute them at runtime.
The Problem
Some things can’t happen during docker build. Maybe you need environment variables that only exist at runtime. Maybe you need to run migrations against a database that isn’t available yet. Maybe you need to generate config files from templates.
The instinct is to shove everything into the entrypoint script. But then your entrypoint becomes a 200-line monster that’s impossible to debug.
The Pattern
Split it into two phases:
# Build time: COPY or CREATE the scripts
COPY docker/post-install/*.sh /docker-entrypoint.d/
RUN chmod +x /docker-entrypoint.d/*.sh
#!/bin/bash
# entrypoint.sh β Runtime: execute the hooks
for f in /docker-entrypoint.d/*.sh; do
echo "Running post-install hook: $f"
bash "$f"
done
exec "$@"
Why This Works
Each hook is a single-purpose script. 01-generate-config.sh renders templates from env vars. 02-run-migrations.sh handles database setup. 03-create-cache-dirs.sh ensures directories exist with correct permissions.
You can test each hook independently. You can add new ones without touching the entrypoint. And if one fails, the error message tells you exactly which hook broke.
The Key Insight
Build time is for things that are static β installing packages, copying files, compiling assets. Runtime is for things that depend on the environment β config generation, service discovery, data setup.
The hook directory pattern bridges the two. Your Dockerfile prepares the hooks. Your entrypoint runs them. Clean separation, easy debugging.
If you’ve used the official Nginx or PostgreSQL Docker images, you’ve already seen this pattern β they use /docker-entrypoint-initdb.d/ for the exact same reason.

Leave a Reply