Docker in CI/CD
Build, scan, sign, and push from a pipeline.
Docker’s real home is the CI/CD pipeline: every push builds an image, tests run inside a known-good container, the image is scanned and signed, and only then does it reach a registry. The pipeline is where the build, scan, sign, verify chain from the supply-chain world actually executes — so a pull request that would ship a vulnerable or unsigned image fails before merge, not in production.
build:stage: buildscript:- docker build -t "$IMG:$CI_COMMIT_SHA" . # tag by commit SHA- docker save "$IMG:$CI_COMMIT_SHA" -o image.tar # hand off to later stagesartifacts: { paths: [image.tar] }scan:stage: testimage: { name: aquasec/trivy:0.53.0, entrypoint: [""] } # pin the scannerscript:- trivy image --input image.tar --exit-code 0 --severity HIGH- trivy image --input image.tar --exit-code 1 --severity CRITICAL --ignore-unfixed
Building inside CI: DinD vs the socket
A CI job needs a Docker engine to build. Two common approaches: Docker-in-Docker (a dind service container running its own daemon, isolated per job) or bind-mounting the host’s /var/run/docker.sock into the job. The socket is faster and simpler but is root-equivalent access to the host — a malicious build could take over the runner. Prefer DinD, or a daemonless builder like BuildKit/buildx or Kaniko that builds images without a privileged daemon at all.
build:image: docker:27services: ["docker:27-dind"] # isolated daemon per job (safer than the socket)variables: { DOCKER_TLS_CERTDIR: "/certs" }script:- docker build -t "$IMG:$CI_COMMIT_SHA" .# daemonless alternative: gcr.io/kaniko-project/executor — no privileged daemon needed
Tag by commit, sign, then push
Tag the image with the commit SHA (immutable, traceable to source) rather than a floating name, sign it with Cosign so downstream admission control can verify provenance, and push only after tests and scans pass. The registry then holds a set of images each tied to a commit, each scanned and signed — the input a Kubernetes cluster’s signature-verification policy expects.
sign_push:stage: deployscript:- echo "$REGISTRY_TOKEN" | docker login "$CI_REGISTRY" -u ci --password-stdin- docker push "$IMG:$CI_COMMIT_SHA"- cosign sign --yes "$IMG@$(docker inspect --format='{{index .RepoDigests 0}}' "$IMG:$CI_COMMIT_SHA" | cut -d@ -f2)"rules: [{ if: '$CI_COMMIT_BRANCH == "main"' }]