Developer friendly multi-mode Docker image entrypoint
Context
Most reusable Docker images from Docker Hub or similar repositories are
designed to run a single process (e.g. database, web server, cache storage).
Expectation is that you can run it with reasonable defaults using
docker run <image>:latest
. And most of customisation is done through
environment variables. That default mode command most of the time will be
configured through ENTRYPOINT.
In case of Rails apps we often find ourselves using the same Docker image to launch multiple process types: web request serving process, delayed job or sidekiq job queue and console process to do some investigation/debugging. Some call it a multi-mode image.
Because of that we do not have a single default command, but rather these:
bundle exec rails server -b 0.0.0.0
bundle exec sidekiq
Occasionally we also use these one-off commands as well:
bundle exec rails db:migrate
bundle exec rails console
And sometimes, we may occasionally run some arbitrary commands to start a shell or inspect docker image itself:
/bin/bash
find /app
In these cases developers often do away with ENTRYPOINT
and CMD
leaving
responsibility of providing correct command to whoever is running the docker
image.
Challenge
Can we pack this mode “knowledge” into the image whilst allowing easy arbitrary commands?
Solution
Use entry point script with selectable mode and fallback to regular commands:
#!/usr/bin/env bash
set -e
case $1 in
"migrate")
exec bundle exec rails db:migrate
;;
"console")
exec bundle exec rails console
;;
"web")
exec bundle exec rails server -b 0.0.0.0
;;
"sidekiq")
exec bundle exec sidekiq
;;
*)
# Otherwise just shell exec the whole thing
exec bash -c "${*}"
;;
esac
With Dockerfile
:
# ... skipped ..
ENTRYPOINT ["/app/bin/docker-entrypoint.sh"]
CMD ["console"]
With this our development environment docker-compose.yml
becomes more like:
services:
web:
command: web
sidekiq:
command: sidekiq
And production k8s deployment:
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: web
args: ["web"]
- name: sidekiq
args: ["sidekiq"]
As you can see mode commands are now contained in a single place and do not spill over into infrastructure code/configuration.
Meanwhile developers are able to easily run mode or arbitrary commands without overriding entrypoint or similar.
Mode command example:
docker-compose run web console
Arbitrary command examples:
docker-compose run web /bin/bash
docker-compose run web bundle exec rails routes
All in all a seemingly simple change that brings noticeable day to day convenience!