Skip to content

Container Images

Images in use

Every reference in .github/workflows/ci.yml, .github/workflows/docs-bundle.yml, .github/workflows/lint-workflows.yml, .devcontainer/docker-compose.yml and ci/docker-compose.bdd.yml is pinned by digest - <repo>@sha256:.... The tag below is the readable handle; the digest in the files is what actually resolves. The tag is kept alongside each reference: as a trailing comment on a container: or Compose image: key, and in the comment above the step for a docker run invocation, whose line continuation cannot carry one. Digests are deliberately not repeated here, so there is one authoritative copy per reference and nothing to drift.

For the same reason the table says what each image is for rather than listing what uses it. Those files already state that executably, and a prose copy of the answer can only fall behind them - which it twice did, once when the lwIP lanes arrived and once when the consumer-smoke ones did. Grep the image name to find its consumers.

Image Tag What it is for
ghcr.io/cososo-ltd/cpputest sha-6715942 The GCC base: CppUTest, CMake and the analysis tooling for host builds, tests and the static-analysis lanes. Ships include-what-you-use 0.23 (clang_19) matching the cpputest-clang build, so an iwyu run behaves the same in either
ghcr.io/cososo-ltd/cpputest-clang sha-5905aea The Clang counterpart to the base, for the portability build and the IWYU lane
ghcr.io/cososo-ltd/cpputest-freertos sha-1739f5b Host-TDD of the embedded adapters against fakes, and the integration suites that need a real upstream. Adds the FreeRTOS-Kernel, Plus-TCP, Plus-FAT, lwIP, FatFs, LittleFS, Mbed TLS, CMSIS_6 and CMSIS-FreeRTOS sources, and inherits IWYU from the cpputest base
ghcr.io/cososo-ltd/cpputest-freertos-cross sha-1739f5b Cross-building the QEMU BDD targets and running them: everything the host image carries, plus gcc-arm-none-eabi, libnewlib-arm-none-eabi, gdb-multiarch (aliased as arm-none-eabi-gdb), qemu-system-arm, and python3 + behave so one image both builds a target and drives it. Upstream sources sit at /opt/freertos/kernel, /opt/freertos/plus-tcp, /opt/freertos/plus-fat, /opt/lwip, /opt/fatfs, /opt/littlefs, /opt/mbedtls, /opt/cmsis and /opt/cmsis-freertos
balabit/syslog-ng 4.8.2 The BDD test oracle, one instance per target so lanes cannot contend. Pinned to the 4.8 LTS line; 4.11.0 (latest as of 2026-02-24) regressed by aborting on STATS over the control socket, which crashed the oracle and cascaded to the dev-container network when a target shares the namespace
ghcr.io/cososo-ltd/behave sha-be8da62 Driving BDD scenarios against a host-built example: Debian trixie, Python 3.12 and Behave. A QEMU target is driven from cpputest-freertos-cross instead, which carries Behave alongside the emulator
davidanson/markdownlint-cli2 v0.22.1 Linting Markdown, in the analyze-markdown lane and in the pre-push command docs/local-checks.md gives. Upstream, so the tag is the release rather than a source commit
rhysd/actionlint 1.7.7 Validating the workflow files, in the lane that has to live outside ci.yml to survive a break in it. Upstream, same tag form
ghcr.io/cososo-ltd/mkdocs-mkdoxy sha-34173c0 Building the documentation site: Doxygen 1.9.4, mkdocs 1.6.1, mkdocs-material 9.7.6, mkdoxy 1.2.8 and mkdocs-github-admonitions-plugin 0.1.1. mkdoxy renders Core/Interface/*.h as native Material API pages

Docker Compose setup

The devcontainer uses Docker Compose (.devcontainer/docker-compose.yml). VS Code connects to the gcc service (GCC). The clang service is on-demand only: it starts when you explicitly run a command against it and stops when done.

BDD testing pairs each target with its own syslog-ng oracle so jobs running in parallel (or developers switching containers) never interfere:

Target Behave runner Oracle
Linux behave-linux syslog-ng-linux
FreeRTOS inside freertos-target (image carries both QEMU and Behave) syslog-ng-freertos

The gcc service depends on syslog-ng-linux so it starts automatically with the devcontainer; the freertos-target service depends on syslog-ng-freertos and shares its network namespace via network_mode: service:syslog-ng-freertos, so QEMU's slirp gateway 10.0.2.2 NATs to the pair's loopback where syslog-ng-freertos is listening on 0.0.0.0:5514. Both oracles also alias as the bare hostname syslog-ng on their network so the existing BDD target wiring (Bdd/Targets/Linux/BddTarget*Config.c, the BDD step helpers) keeps resolving without per-target host overrides. The pairs never run together, so the alias collision is academic.

As more cross-compilation targets are added, each gets its own oracle pair in the same shape (syslog-ng-<target> + a runner service or in-container Behave).

FreeRTOS networking backend selection

cpputest-freertos and cpputest-freertos-cross both ship the FreeRTOS-Plus-TCP and lwIP source trees side-by-side, and a default build selects both - you can work on either networking stack, and run both stacks' unit tests, without reconfiguring. The configure says so:

-- SolidSyslog tests: both FreeRTOS networking stacks selected — lwIP and Plus-TCP suites build together.

To work in one stack only, deselect the other with its own switch (-DSOLIDSYSLOG_PLUSTCP=OFF, or -DSOLIDSYSLOG_LWIPRAW=OFF with -DSOLIDSYSLOG_LWIPSOCKET=OFF), which drops that platform and its tests together. The lwIP lint lanes do exactly this. The switch holds under the default Auto selection; with a named list, leave the platform out of the list instead.

Which BDD ELF a cross build produces is separate, and maintainer-only - SOLIDSYSLOG_BDD_TARGET=FREERTOS_PLUSTCP (default), FREERTOS_LWIP or CMSIS_LWIP. It selects a test artefact, not a platform - platforms are named in SOLIDSYSLOG_PLATFORMS like everything else.

CI runs each cross target in isolation, and all three of build-freertos-target-plustcp, build-freertos-target-lwip and build-cmsis-target-lwip are required checks.

Running the clang build locally

From a host terminal (not inside the devcontainer):

docker compose -f .devcontainer/docker-compose.yml run --rm clang \
    cmake --preset clang-debug

docker compose -f .devcontainer/docker-compose.yml run --rm clang \
    cmake --build --preset clang-debug --target junit

Updating an image

When a new image tag is available:

  1. Build and push the new image in the container image repo
  2. Resolve the new tag to its digest:
docker buildx imagetools inspect ghcr.io/cososo-ltd/<image>:sha-<new> \
  --format '{{.Manifest.Digest}}'

Pin exactly what this command prints - the image index digest for a multi-architecture image, or the single manifest digest for a single-platform one. Do not substitute a per-platform digest dug out of --raw: for an index that would nail the reference to one architecture. Of the images here, only balabit/syslog-ng is genuinely multi-arch (linux/amd64 and linux/arm64); the rest are linux/amd64 only.

  1. Update the digest and the accompanying tag comment in every file that references the image (see table below), plus the tag in docs/containers.md
  2. Rebuild the devcontainer (Ctrl+Shift+P → "Dev Containers: Rebuild Container") and verify locally
  3. Raise a PR: use chore: bump container image to <sha> as the title
Image Files to update
cpputest .devcontainer/docker-compose.yml, .github/workflows/ci.yml, docs/containers.md
cpputest-clang .devcontainer/docker-compose.yml, .github/workflows/ci.yml, docs/containers.md
cpputest-freertos .devcontainer/docker-compose.yml, .github/workflows/ci.yml, docs/containers.md
cpputest-freertos-cross .devcontainer/docker-compose.yml, .github/workflows/ci.yml, ci/docker-compose.bdd.yml, docs/containers.md
behave .devcontainer/docker-compose.yml, ci/docker-compose.bdd.yml, docs/bdd.md, docs/containers.md
mkdocs-mkdoxy .github/workflows/ci.yml, .github/workflows/docs-bundle.yml, docs/containers.md
markdownlint-cli2 .github/workflows/ci.yml, docs/local-checks.md, docs/containers.md
actionlint .github/workflows/lint-workflows.yml, docs/containers.md
syslog-ng .devcontainer/docker-compose.yml, ci/docker-compose.bdd.yml, docs/containers.md

syslog-ng, markdownlint-cli2 and actionlint are upstream images rather than ours, published by balabit, DavidAnson and rhysd. Step 1 does not apply to any of them: the version is chosen from what upstream published and pinned, not built. Read the 4.8 LTS pinning rationale in the first table before moving syslog-ng.

The cpputest-freertos and cpputest-freertos-cross images both come from CppUTestFreertosDocker. A single push to that repo's main rebuilds and publishes both images at the same sha-<short> tag; always update both rows together.

The mkdocs-mkdoxy image comes from MkdocsMkdoxyDocker; a push to that repo's main rebuilds and publishes it at a new sha-<short> tag.

mkdocs-mkdoxy is a CI-only image; it is not a devcontainer service, so the generic "rebuild the devcontainer and verify" step above does not exercise it. Verify a tag bump by running the same command the docs-build job runs, from the repo root, before raising the bump PR:

docker run --rm -v "$PWD:/docs" \
  ghcr.io/cososo-ltd/mkdocs-mkdoxy@sha256:<new> mkdocs build --strict

All references to a given image must use the same digest. Never update one without the others. To check that invariant across the digest-pinned files:

grep -rhno 'ghcr.io/cososo-ltd/[a-z-]*@sha256:[0-9a-f]*\|balabit/syslog-ng@sha256:[0-9a-f]*\|davidanson/markdownlint-cli2@sha256:[0-9a-f]*\|rhysd/actionlint@sha256:[0-9a-f]*' \
  .github/workflows/ci.yml .github/workflows/docs-bundle.yml \
  .github/workflows/lint-workflows.yml \
  .devcontainer/docker-compose.yml ci/docker-compose.bdd.yml \
  | sed 's/^[0-9]*://' | sort -u

One line per image means every reference agrees; two lines for the same image name means a bump was applied unevenly.

The audit covers digest-pinned references only. docs/local-checks.md invokes markdownlint-cli2 by tag, deliberately: it is a command a developer types, where a digest is unreadable and the engine version is the thing worth stating. That line is out of this check by design, so leave it out when extending the pattern.

Switching to a different container as the devcontainer

The available services and the build preset each one drives:

Service Use case BUILD_PRESET
gcc Primary C/C++ development (default) debug
clang Clang-specific debugging / portability clang-debug
freertos-host TDD of FreeRTOS adapters against host-side fakes debug
freertos-target Cross builds, on-QEMU runs, GDB attach (Cortex-M3, mps2-an385), BDD against the QEMU target freertos-cross
behave-linux Linux BDD scenario development (Python + Behave) (none - cmake skipped)

To switch:

  1. In .devcontainer/devcontainer.json, change "service": "gcc" to the target service name (e.g. "freertos-target").
  2. Ctrl+Shift+P → "Dev Containers: Rebuild Container".
  3. Work normally; Ctrl+Shift+B and all other tasks pick up the right preset via $BUILD_PRESET.

When done, revert "service" back to "gcc" and rebuild again.

The same VS Code keys work across every service:

  • Ctrl+Shift+B runs the build and test task, which adapts to the active BUILD_PRESET. Under freertos-cross it builds the SolidSyslogBddTarget ELF; under debug / clang-debug it builds and runs SolidSyslogTests; with BUILD_PRESET empty (the behave-linux service) it runs behave.
  • F5 debugs:
  • Debug SolidSyslogTests (host): works in gcc, clang, and freertos-host (path resolves via ${env:BUILD_PRESET}). Builds first via the same build and test task and stops at main.
  • Debug FreeRTOS BDD Target (QEMU): works in freertos-target (cortex-debug + arm-none-eabi-gdb + qemu-system-arm). Stops at main via runToEntryPoint.
  • After switching the devcontainer service, pick the matching config from the Run-and-Debug dropdown once. VS Code remembers the last-picked config per workspace (not per container), so the previous choice survives a container rebuild; a stale selection will fail with the wrong debugger type.
  • Ctrl+Shift+P → "Tasks: Run Task" → run on QEMU (FreeRTOS): one-shot QEMU run for sanity-checking the build, output to the integrated terminal. Use only in the freertos-target service.

For the FreeRTOS BDD target, see Bdd/Targets/FreeRtos/README.md for build / run / GDB-attach instructions.