If you want to create a single Docker image that can do different things, one useful pattern is the "multi-mode" image. This practice removes the need to create two different images with the same build steps that only differ in the running command.
The concept boils down to identifying the different run modes and mapping them to verbs. You then use a simple argument-parsing script as an entrypoint to do the right thing:
#!/usr/bin/env bash
case $1 in
'server')
echo "I'm a server! Here are my args:" "${@:2}"
;;
'daemon')
echo "I'm a daemon! Here are my args:" "${@:2}"
;;
*)
printf 'unknown verb %s\n' >&2
exit 1
;;
esac
The script is then set as the image's ENTRYPOINT like so:
# syntax=docker/dockerfile:1
FROM alpine:3.19
RUN apk add --no-cache bash
COPY docker-entrypoint /usr/local/bin
ENTRYPOINT ["docker-entrypoint"]
Creating and running a container with the above image
(e.g., docker run multi-mode-example) will result in the "unknown verb" error.
A default verb can be set with CMD.
Below is an example of how to use this image in a Compose stack:
services:
server:
build: .
command: ["server", "--port", "8080"]
daemon:
build: .
command: ["daemon", "--debug"]
Backstory
I came across this idea while following "Deploying Dagster to Docker" and peeking into
their example on GitHub which uses the same image for two different services.
They do so by declaring a different entrypoint on each service.
Trying to deeply understand ENTRYPOINT vs CMD, I landed on a very good blog post
by Michael Fischer which formally introduced me to the pattern of multi-mode images.