rules_docker_compose

Bazel rules for running integration tests which use Docker-Compose to setup networked infrastructure.

Setup

bazel_dep(name = "rules_docker_compose", version = "{version}")

docker_compose = use_extension("@rules_docker_compose//docker_compose:extensions.bzl", "docker_compose")
docker_compose.toolchain(
    name = "docker_compose_toolchains",
    version = "2.24.0",
)
use_repo(docker_compose, "docker_compose_toolchains")

register_toolchains(
    "@docker_compose_toolchains//:all",
)

Overview

rules_docker_compose provides Bazel rules for integrating Docker-Compose into your build and test workflows. These rules enable you to:

  • Merge and validate docker-compose configurations using the docker_compose_yaml rule, which combines multiple YAML files and ensures all referenced images have corresponding loader targets
  • Run integration tests with the docker_compose_test rule, which automatically starts services, waits for them to be ready, runs your test binary, and cleans up containers
  • Verify image integrity through automatically generated lock files that map image tags to content digests, ensuring the correct images are loaded at runtime

The rules integrate seamlessly with rules_oci and rules_img, allowing you to use container images built with Bazel in your docker-compose configurations. They handle the complete lifecycle of docker-compose services during testing, including image loading, service startup, health checking, and cleanup.

Why not to use these rules

These rules are designed for integration testing scenarios where you need to test against real networked services. However, they come with trade-offs that may not be suitable for all use cases:

  • Requires network access: Tests must be marked with requires-network and cannot run in fully sandboxed environments. This means tests may be slower and less reproducible than pure unit tests.

  • Docker dependency: Tests require Docker (or compatible container runtime) to be available on the host machine. This adds an external dependency and may not work in all CI/CD environments.

  • Resource tracking limitations: Bazel cannot track all resources created by docker-compose (spawned containers, networks, volumes). It's highly recommended to use resource tags to limit test execution and prevent resource exhaustion.

  • Port collisions: Tests that bind to the same host ports cannot run in parallel. If multiple tests use the same port mappings (e.g., "8080:80"), they will conflict when Bazel attempts to run them concurrently. Use unique port mappings per test or ensure tests are tagged to run serially.

  • Platform-specific behavior: Container behavior may vary across different platforms and Docker versions, potentially affecting test reproducibility.

Consider using these rules when you need to test interactions with real services, but prefer lighter-weight alternatives for fast, hermetic unit tests.

Docker Compose

Rules

docker_compose_test

load("@rules_docker_compose//docker_compose:defs.bzl", "docker_compose_test")

docker_compose_test(name, data, delay, env, env_inherit, images, test, test_args, yamls)

Runs a test with docker-compose services.

This rule starts docker-compose services, waits for them to be ready, runs a test binary, and then tears down the services. The test lifecycle is:

  1. Loading container images into Docker using the specified loader targets
  2. Starting services with docker-compose up
  3. Waiting for containers to be running (with health checks)
  4. Verifying image digests match the expected values from the lock file
  5. Running the test binary with optional arguments
  6. Cleaning up with docker-compose down

The test requires network access and Docker to be available on the host.

Supported image loader types:

LoaderSource
oci_loadrules_oci
image_loadrules_img

Example:

load("@rules_docker_compose//docker_compose:docker_compose_test.bzl", "docker_compose_test")
load("@rules_oci//oci:defs.bzl", "oci_load")

oci_load(
    name = "my_service.load",
    image = ":my_service",
    repo_tags = ["my-service:latest"],
)

docker_compose_test(
    name = "integration_test",
    yamls = ["docker-compose.yaml"],
    images = [":my_service.load"],
    test = ":my_test_binary",
    test_args = ["-host", "localhost:8080"],
    delay = 2,  # Wait 2 seconds after containers start
)

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
dataAdditional runtime dependencies for the test.List of labelsoptional[]
delaySeconds to wait after containers are running before executing the test. Useful for services that need initialization time beyond their health checks.Integeroptional1
envDictionary of strings; values are subject to $(location) and "Make variable" substitutionDictionary: String -> Stringoptional{}
env_inheritSpecifies additional environment variables to inherit from the external environment when the test is executed by bazel test.List of stringsoptional[]
imagesImage loader targets that provide the container images for the docker-compose services. Each image will be loaded into Docker before docker-compose up is called. See the rule documentation for supported loader types.List of labelsoptional[]
testThe test binary to execute after containers are running. The binary will receive arguments from test_args.LabeloptionalNone
test_argsArguments to pass to the test binary.List of stringsoptional[]
yamlsOne or more docker-compose YAML files defining the services to run. Files are merged using docker-compose config.List of labelsoptional[]

docker_compose_toolchain

load("@rules_docker_compose//docker_compose:defs.bzl", "docker_compose_toolchain")

docker_compose_toolchain(name, digest_mode, docker_compose, version)

A toolchain for providing Docker-Compose to Bazel rules.

The digest_mode attribute controls how image digests are computed for lock files:

ModeDescription
ociUses the OCI manifest digest directly from the image's index.json. Use this when images are pushed to an OCI-compliant registry.
docker-legacyUses the config blob digest from the OCI manifest. This is the image ID that Docker reports after docker load when using legacy storage (without containerd). Use this for Linux CI runners like GitHub Actions.
docker-containerdConverts the OCI manifest to Docker V2 Schema 2 format and computes the manifest digest. This is the image ID that Docker reports after docker load when using containerd storage. Use this for Docker Desktop or Docker Engine with containerd enabled.

See the OCI Image Manifest spec and the Docker V2 Schema 2 spec.

Example:

load("@rules_docker_compose//docker_compose:docker_compose_toolchain.bzl", "docker_compose_toolchain")

filegroup(
    name = "docker_compose_bin",
    srcs = ["docker_compose/docker_compose.exe"],
    # Note that additional runfiles associated with a hermetic archive
    # of docker_compose should be associated with the target passed to the
    # `docker_compose` attribute.
    data = glob(["docker_compose/**"]),
)

docker_compose_toolchain(
    name = "docker_compose_toolchain",
    docker_compose = ":docker_compose_bin",
    visibility = ["//visibility:public"],
)

For users looking to use a system install of Docker-Compose, a shell/batch script should be added that points to the system install.

Example or non-hermetic toolchain:

docker_compose.sh

#!/usr/bin/env bash
set -euo pipefail
exec /usr/bin/docker_compose $@

docker_compose.bat

@ECHO OFF
C:\Program Files\Docker\Docker\resources\docker-compose.exe %*
set EXITCODE=%ERRORLEVEL%
exit /b %EXITCODE%
load("@rules_docker_compose//docker_compose:docker_compose_toolchain.bzl", "docker_compose_toolchain")

filegroup(
    name = "docker_compose_bin",
    srcs = select({
        "@platforms//os:windows": ["docker_compose.bat"],
        "//conditions:default": ["docker_compose.sh"],
    }),
)

docker_compose_toolchain(
    name = "docker_compose_toolchain",
    docker_compose = ":docker_compose_bin",
    visibility = ["//visibility:public"],
)

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
digest_modeControls how image digests are computed for lock files. See the rule documentation for details on available modes. Defaults to the value of --@rules_docker_compose//docker_compose/settings:toolchain_default_digest_mode.Stringoptional""
docker_composeThe docker-compose executable.Labelrequired
versionThe version of docker-compose.Stringrequired

docker_compose_yaml

load("@rules_docker_compose//docker_compose:defs.bzl", "docker_compose_yaml")

docker_compose_yaml(name, out, images, yamls)

Merges multiple docker-compose YAML files and validates image references.

This rule uses docker-compose config to merge one or more docker-compose YAML files into a single, canonical configuration. It also validates that all images referenced in the configuration have corresponding loader targets specified in the images attribute.

A lock file is generated containing a mapping of image tags to their content digests, which can be used to verify that the correct images are loaded at runtime.

Supported image loader types:

LoaderSource
oci_loadrules_oci
image_loadrules_img

Example:

load("@rules_docker_compose//docker_compose:docker_compose_yaml.bzl", "docker_compose_yaml")
load("@rules_oci//oci:defs.bzl", "oci_load")

oci_load(
    name = "my_image.load",
    image = ":my_image",
    repo_tags = ["my-app:latest"],
)

docker_compose_yaml(
    name = "compose",
    yamls = ["docker-compose.yaml"],
    images = [":my_image.load"],
)

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
outOptional output filename for the merged docker-compose YAML. Defaults to {name}/docker-compose.yaml.Label; nonconfigurableoptionalNone
imagesImage loader targets that provide the container images referenced in the YAML files. Each image referenced in the docker-compose YAML must have a corresponding loader target. See the rule documentation for supported loader types.List of labelsoptional[]
yamlsOne or more docker-compose YAML files to merge. Files are merged in order using docker-compose config.List of labelsrequired