Composeλ︎
Docker Compose provides a declarative configuration for orchestrating multiple services locally.
Compose can build images for Clojure projects using a Dockerfile and conditionally run services based on the health check of other supporting services, e.g. Clojure service runs once Postgres Database service is healthy.
Each service can define a heart beat used as a conditional startup for other services which depend upon them.
Compose Configurationλ︎
compose.yaml is the configuration file for Docker Compose
docker compose command will start all services defined in the compose.yaml configuration (or --file filename to specify an alternative configuration file).
--buildoption will run the build process for services that contain abuild:configuration--detachruns services in the background, compose startup logs are still shown in the foreground
Shutdown the services with down (specifying --file filename if used in the compose up command)
Compose simplified after Docker integration
compose.yaml is the new Compose configuration file for orchestrating services locally, a simplified and extended version of docker-compose.yaml. The main benefit is that a version number is no longer required, as the Compose version shipped with Docker is used.
docker compose the new command, replacing docker-compose, although the options and arguments to this command are the same for now.
Build Clojure Serviceλ︎
The simplest approach to building a service is to include a build: configuration specifying the location of a multi-stage Dockerfile, which has a builder stage to create an uberjar that is run in the run-time image.
The build: option for the Clojure service with the path to the multi-stage Dockerfile for the project (typically in the same root directory of the project, although a remote Git repository can also be used)
Clojure project build configuration
# --- Docker Compose Configuration --- #
# - Docker Compose V2
# - https://docs.docker.com/compose/compose-file/
#
# Build the Clojure Service from source code
# and run on port 8080
name: "practicalli"
services:
# --- Clojure Service --- #
gameboard-service:
platform: linux/amd64
# Build using Dockerfile - relative path or Git repository
build:
context: ./ # Use Dockerfile in project root
environment:
- COMPOSE_PROJECT_NAME
ports: # host:container
- 8080:8080
name:(optional) is used to set the container prefix of the service name, via theenvironment:variableCOMPOSE_PROJECT_NAME, helping to identify the container by name when running. The above container would bepracticalli-gameboard-service. This option is more valuable as the number of services growsservices:contains one or more service definitions with a unique name, e.g.gameboard-servicegameboard-service:is a unique name for a service configurationplatform:defines the operating system and hardware architecture that should be used for the servicebuild: context:defines the location of theDockerfileto use, either a local file or a Git repository URLports:defines the host:container mapping for the service port.8080:8000value would map the service running within the container on8000to the host (e.g. engineers' computer) on port 8080.
Use a multi-stage
Dockerfileto provide greater opportunity to create image overlays for the build stage which can be cached, e.g. downloading project dependencies, speeding up the build process on consecutive runs.
Compose servicesλ︎
Add more unique service configurations under services:, e.g. postgres-database. All the services that support the local testing and integration of the Clojure project can be added to the compose.yaml file (database, cache, mock APIs, coupled services, etc.).
The Clojure service defines a dependency on a Postgres Database. The dependency has a condition so the Clojure service is only started once the Postgres service is healthy
Compose Clojure project build with Postgres database
services:
clojure-service:
platform: linux/amd64
build: ./
ports: # host:container
- 8080:8080
depends_on:
postgres-database:
condition: service_healthy
postgres-database:
image: postgres:15.2-alpine
environment:
POSTGRES_PASSWORD: "$DOCKER_POSTGRES_ROOT_PASSWORD"
healthcheck:
test: [ "CMD", "pg_isready" ]
timeout: 45s
interval: 10s
retries: 10
ports:
- 5432:5432
depends_on:include one or more services that the service depends on, optionally adding a startcondition:depends_on: <service-name> condition:a condition that should be true in order for the service image to start, typically checkingservice_healthyof another service in the configurationheathcheck:define a command and options to determine if the specific service running is healthy, with thetest:command specific to the type of service.
Build and run the services using the --build flag with docker compose
The Clojure project is built in parallel with running the Postgres database.
On the initial run the Clojure project may take longer to run that starting the Postgres database, in which case the Clojure uberjar will run straight away.
On consecutive runs, at least part of the Clojure build process should be cached and images for all the services will have been downloaded and cached. The Clojure uberjar may wait for the Postgres database to start up.
Each service can define a health check that can be used as a conditional startup trigger and ensure all services start in a meaningful order.
Build on Changeλ︎
Docker provides watch as an experimental feature which can rebuild the Clojure service when a file change is detected.
The watch approach seems most useful for Clojure projects when troubleshooting issues that occur during system integration testing.
When additional tools need to run outside of Clojure code, e.g. SaSS build, data loading / ETL, then watch can compliment the Clojure REPL workflow by providing incremental change management for non-clojur aspects of the project.
Add an x-develop configuration with watch under the Clojure service configuration. Define the action for each path to create a simple but comprehensive way to update the service
Clojure Project - Build on Change
Start the services and the file watch mode
Save changes to files and a new image for the Clojure service will be built and deployed when ready.
Optimise Docker Cache in Build process
Using build on change approach can run quite frequently, so an optimised build process in the Dockerfile or compose.yaml configuration is especially important to make build on change fast and therefore effective to use.
Compose servicesλ︎
Define a compose.yaml file that builds the Clojure project and run services that the Clojure service requires or talks too (database, cache, mock API, etc.).
Each service can define a heart beat which can be used as a conditional startup for other services.
compose.yaml new Compose configuration file
compose.yaml is the new configuration file for orchestrating services locally, a simplified and extended version of docker-compose.yaml.
Include the build: option for the Clojure service with the path to the multi-stage Dockerfile for the project (typically in the same root directory of the project, although a remote Git repository can also be used)
The Clojure service defines a dependency on a Postgres Database. The dependency has a condition so the Clojure service is only started once the Postgres service is healthy
Clojure Service with Postgres Database
services:
clojure-service:
platform: linux/amd64
build: ./
ports: # host:container
- 8080:8080
depends_on:
postgres-database:
condition: service_healthy
postgres-database:
image: postgres:15.2-alpine
environment:
POSTGRES_PASSWORD: "$DOCKER_POSTGRES_ROOT_PASSWORD"
healthcheck:
test: [ "CMD", "pg_isready" ]
timeout: 45s
interval: 10s
retries: 10
ports:
- 5432:5432
Run the services using docker from the root of the project
File watcherλ︎
Docker provides watch as an experimental feature which can rebuild the Clojure service when a file change is detected. This seems most useful when troubleshooting issues that occur during system integration testing.
Add an x-develop configuration with watch under the Clojure service configuration
Automated rebuild on file change
Start the services and the file watch mode
Save changes to files and a new image for the Clojure service will be built and deployed when ready.


