Skip to content

Dockerfileλ︎

Docker logo

A Dockerfile is a declarative configuration that defines how an image is assembled and can use another image to provide much of the configuration, minimising the amount of design and maintenance required.

Each line in the Dockerfile configuration creates an overlay, a layer of the image that is applied to create the final image. Overlays are cached by docker on the first run and if the files or commands the overlays are created from do not change, then the cache is used on consecutive use of the docker image.

A Multi-stage Dockerfile is an effective way to build and run projects via a continuous integration pipeline and local development (in concert with supporting services via a compose.yaml configuration).

Docker Hub provides a wide range of images, supporting development, continuous integration and system integration testing.

Multi-stage Dockerfileλ︎

A multi-stage Dockerfile contains builder stage and an unnamed stage used as the run-time. The builder stage can be designed optimally for building the Clojure project and the run-time stage optimised for running the service efficiently and securely.

The builder stage caches dependencies to optimise building Clojure and the run-time stage optimises running the service efficiently and securely.

The uberjar created by the builder image is copied over to the run-time image to keep that image as clean and small as possible (to minimise resource use).

Docker Multi-stage build file with cached overlays

Practicalli Multi-stage Dockerfile for Clojure

Practicalli Multi-stage Dockerfile for Clojure projects derived from the configuration currently used for commercial and open source work.

The Dockerfile uses make targets, which are Clojure commands defined in the Practicalli Makefile

Multi-stage Dockerfile for Clojure projects Docker Multi-stage builds docs

Docker init - beta feature

docker init is a new (beta) feature to create Dockerfile, .dockerignore andcompose.yaml files using Docker recommended practices.

Docker Build overview

Official Docker imagesλ︎

Docker Hub contains a large variety of images, using those tagged with Docker Official Image is recommended.

Ideally a base image should be used where both builder and run-time images share the same ancestor, this helps maintain consistency between build and run-time environments.

The Eclipse OpenJDK image is used by the Clojure docker image, so they implicitly use the same base image without needed to be specified in the project Dockerfile. The Eclipse OpenJDK image could be used as a base image in the Dockerfile but it would mean repeating (and maintaining) much the work done by the official Clojure image)

Alternative Docker images

An Official Docker Image means the configuration of that image follows the Docker recommended practices, is well documented and designed for common use cases. There is no implication at to the correctness of tools, languages or service that image provides, only in the means in which they are provided.

Docker initλ︎

docker init provides a command line wizard to create set of Docker configurations that follow the recommended practices

docker init

Specific configurations are provides for Go and Python, other languages should use the General Purpose option.

The General purpose option provides almost all the configuration required. Update the RUN command in the build stage to that of the specific language, ideally copying the project dependency file to the build stage and inititing a dry run to download the dependency files to create a Docker overlay cache of dependencies.

Dockerfile Syntaxλ︎

The form of Dockerfile instructions

# Comment
INSTRUCTION arguments

# at the start of a line defines a line comment. # within a line is considered an argument to the instruction.

\ is a line continuation character, allowing a cleaner format when using longer commands as instruction arguments.

HEALTHCHECK \
    CMD ["curl", "--fail", "http://localhost:8080/system-admin/status"]

Start by learning the common instructions used in Dockerfile configurations and consult 🌐 Docker Docs extensive Dockerfile reference to understand additional instructions are required

🌐 Docker Docs Dockerfile reference

FROMλ︎

FROM instruction should be the first instruction in the Dockerfile and specifies the image from which the current configuration uses as a base.

FROM can be preceded ARG instructions to declare arguments used in the FROM instruction. Comments # and parser directives can also appear before FROM

COPYλ︎

Copy files from the local directory or from a URL to the Docker images.

COPY replaces ADD

Avoid the use of ADD and use COPY instead

WORKDIRλ︎

Set the directory in the Docker image in which all future commands will run.

Setting WORKDIR can simplify COPY and RUN commands

USERλ︎

Set user account to run all further commands

RUNλ︎

Execute any command recongnised within the container

HEALTHCHECKλ︎

Docker Service heathcheck provides a simple mechanism to report that a service is running. A health check is a vital configuration to provide for production systems to ensure failing containers are replaced by working containers automatically.

The status of the service can be checked manually using the docker inspect command

docker inspect --format='{{json .State.Health}}' container-name

Define a HEALTHCHECK CMD to identify the current status of the service running within the container, e.g. a curl command to test an API endpoint that demonstrated the service is running.

Heathcheck options include

  • --interval frequency to run the health check
  • --timeout maximum time to wait for a response to the health check command
  • --start-period grace period for the service to start up before the first health check runs
  • --retries number of times to repeat the command before reporting failure (reports .... until success or failure)

Wait 10 seconds before running the first health check, then try every 5 seconds and wait 3 seconds for a response. Run the health check a maximum of 2 times before reporting failure (assuming successful check has not been returned).

--interval=5s --timeout=3s --start-period=10s --retries=2

HEALTHCHECK using shell command

HEALTHCHECK \
  CMD curl --fail http://localhost:8080/system-admin/status || exit 1

HEALTHCHECK using Docker Exec array command

HEALTHCHECK \
    CMD ["curl", "--fail", "http://localhost:8080/system-admin/status"]

Compose can also define a health check

compose.yaml can include a health check for each service defined which can be used by the service being built to conditionally start.

Add a health-check to the compose.yaml configuration when the service being built requires a database or similar service that should be available before starting.

Avoid defining a health-check in the compose.yaml for services that already have an adequate health-check defined in ther Dockerfile

ENTRYPOINTλ︎

Define the default command to run once the container is started.

The image used to create the final stage from may have a default command, e.g. Eclipse Temurin image provides the jshell ENTRYPOINT, which is overridden by supplying an ENTRYPOINT in the Dockerfile for the current project.

ENTRYPOINT is typically used with CMD

Process manager ENTRYPOINT with service CMD

Start dumb-init to manage the CMD process that calls the java run-time. dumb-init ensures SIGTERM signals are sent to the java process ensuring a clean shutdown

ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["java", "-jar", "/service/practicalli-suit-you-sir-standalone.jar"]

🌐 Docker Entrypoint documentation

CMDλ︎

The command to run the service within the container, typically paired with ENTRYPOINT

CMD can be supplied on the command line when calling docker to over-ride the CMD defined in the Dockerfile, e.g. to help test a Docker image that is not running correctly or run variations of the service such as debug mode.