Skip to content

Use Docker in CI

CI jobs on Codebahn’s hosted runners execute inside a container. The Docker daemon runs on the host, and the socket is mounted into the job, so docker commands work. docker build, docker push, docker run all behave as expected.

One thing does not: bind mounts.

When you run a sibling container from a CI job:

- run: docker run -v $(pwd)/report.json:/input/report.json scanner:latest

The -v path resolves on the host, not inside your job container. The host has no /input/report.json at that path, so the mount is empty or fails silently.

This is not a Codebahn limitation. The same thing happens on any runner that executes jobs in containers with a host Docker socket (GitHub Actions hosted runners, GitLab runners in Docker mode, self-hosted Forgejo runners).

Copy files into the container explicitly:

- name: Scan with Trivy
run: |
cid=$(docker create aquasec/trivy image \
--input /scan/image.tar \
--severity HIGH,CRITICAL --exit-code 1)
trap 'docker rm -f "$cid" >/dev/null 2>&1 || true' EXIT
docker cp report.json "$cid:/scan/report.json"
docker start -a "$cid"

docker create makes the container without starting it. docker cp copies files in. docker start -a runs it and attaches stdout.

Most Docker workflows never hit this. If you only:

  • Build images (docker build): the build context is sent to the daemon over the socket, not mounted. Works fine.
  • Push images (docker push): no file exchange needed. Works fine.
  • Run containers that don’t need local files (docker run postgres): no mount needed. Works fine.

The limitation only matters when you need to pass a file from your job into a sibling container. Image scanning, linting a generated artifact, or feeding a report into a processing container are the common cases.

For tools that accept stdin:

- run: docker save myimage:latest | docker run -i aquasec/trivy image --input -

Simpler than docker cp, but not every tool supports stdin input, and large images can be slow over a pipe.