DEV Community

Ebrahim Hoseiny Fadae
Ebrahim Hoseiny Fadae

Posted on

Use Docker Compose setup scripts

Why we need setup scripts?

  1. Reducing time to clean-start the project.
  2. Minimizing the feedback loop.
  3. Self-documented setup scripts.
  4. Facilitate team collaboration.
  5. More automations in CICD pipelines.
  6. Deterministic and version controlled environments.

Does docker have built-in support for setup scripts?

Docker has introduced some features to support this, but they aren't yet powerful enough for more advanced use cases. To achieve more, we define something called Setup Service.

Built-in Docker Compose setup scripts

This feature requires Docker Compose 2.30.0 and later.

Post-start:

According to Docker Compose docs:

Defines a sequence of lifecycle hooks to run after a container has started. The exact timing of when the command is run is not guaranteed.

services:
  test:
    post_start:
      - command: ./do_something_on_startup.sh
        user: root
        privileged: true
        environment:
          - FOO=BAR
Enter fullscreen mode Exit fullscreen mode

If the post_start script fails, the container will terminate with the same exit code.

Pre-stop:

According to Docker Compose docs:

Defines a sequence of lifecycle hooks to run before the container is stopped. These hooks won't run if the container stops by itself or is terminated suddenly.

services:
  test:
    pre_stop:
      - command: ./do_something_on_stop.sh
        user: root
        privileged: true
        environment:
          - FOO=BAR
Enter fullscreen mode Exit fullscreen mode

Limitations

The lifecycle hooks are useful in many scenarios, but they also have some limitations.

  1. Not always, everything we need is in the same container. Sometimes we need different set of tools, the docker cli or even different OS.
  2. If the script needs anything more than some simple shell commands with no extra dependencies, the container is coupled with its setup script, potentially increasing the size and maintenance cost.
  3. We can't apply resource limitation only to setup script.
  4. This feature is currently new, but it won’t be an issue in the future.

References:

Isolated Setup Services

The migrate service contains all the logics for initiating the database. It will restart on failure. Also Docker Compose watch mode is used to make sure this script will be executed every time the database schemas are updated.

services:
    migrate:
        build:
            dockerfile: Dockerfile.migrate # Script execution environment
        command: ./migrate.sh
        deploy:
            restart_policy: # Tune it based on fail rate of the script
                condition: on-failure
                delay: 5s
                max_attempts: 5
                window: 120s
        resources: # Usually, setup requires more resource
            limits:
                memory: 128mb
        develop:
            watch:
                - action: rebuild
                  path: src/db # Change it based on your folder structure
        depends_on:
            postgresql: # Make sure postgresql has healthcheck instruction
                condition: service_healthy
Enter fullscreen mode Exit fullscreen mode

If the script can't finish successfully, the whole project will shutdown only if the main service of the project depends of the migrate service.

Limitations:

  1. There’s no equivalent to a pre_stop script.

Setup scripts are services themselves, so you can use post_start and pre_stop instructions in them! If you’re creative, you can do some really cool things with them :)

Docker-dependent Setup Services

Sometimes we need access to host's docker cli to execute a command.

services:
    setup:
        image: docker:28.1-cli # It is better to matched with host's docker
        environment:
            - COMPOSE_PROJECT_NAME=$COMPOSE_PROJECT_NAME # Required
        command: docker compose exec postgresql ./setup.sh
        volumes:
            - /var/run/docker.sock:/var/run/docker.sock
            - ./docker-compose.yml:/docker-compose.yml
        depends_on:
            postgresql: # Make sure postgresql has healthcheck instruction
                condition: service_healthy
        deploy:
            restart_policy: # Tune it based on fail rate of the script
                condition: on-failure
                delay: 5s
                max_attempts: 5
                window: 120s
        resources:
            limits:
                memory: 256mb
Enter fullscreen mode Exit fullscreen mode

Conclusion

Automation is the remedy for the curse of complexity. We can automate setup scripts in order to hide the complexity of setting up. While Docker provides powerful tools for this, there’s still room for improvement in supporting these workflows more natively.

Top comments (0)