Chapter 15Lesson 03~175 minutes

Service Containers, Job Containers, Docker Builds, and Integration-Test Environments: Configuration, Design Patterns, and Trade-Offs

Containers are a delivery design tool, not one mechanism. This lesson compares service containers with external services, job containers with container actions, host Docker builds with Buildx-based builds, tags with digests, and integration-test environments with actual deployment targets.

Service vs externalJob vs action containerTag vs digestBuildxIsolation

Learning objectives

  • Choose between service containers and external test services based on isolation, fidelity and operational cost.
  • Distinguish a job container from a Docker container action and from a host Docker build.
  • Choose tag or digest identity according to reproducibility and supply-chain requirements.
  • Evaluate host Docker CLI, Buildx and Docker-maintained actions without treating action tags as immutable.
  • Keep integration-test evidence separate from registry publication and production deployment authorization.

1. Start with the invariant, not the container feature

The correct design begins with what must be isolated and proven. If the invariant is “tests need a fresh Redis of a known family for one job,” a service container is usually enough. If the invariant is “the application must run inside the same base userspace on every runner,” a job container may be appropriate. If the invariant is “produce a multi-platform OCI image,” a Buildx workflow is a different problem again.

2. Service container versus external service

Choice Strengths Risks / prerequisites Best evidence
Service container disposable, local to job, predictable teardown, no cloud account image pull/network startup, lower production fidelity image identity, health, connection target, job conclusion
External disposable service closer to managed provider behavior, can test network/auth credentials, cost, cleanup, rate limits, external side effects resource ID, endpoint, auth method, external health, deletion proof
Shared long-lived service fast, existing data/integration surface cross-run contamination, availability coupling, destructive-test risk namespace/tenant guard, immutable test dataset, external audit logs

Mandatory course learning stays with service containers because it is free and bounded. External providers belong to optional authorized labs with explicit rollback.

3. Job container versus container action

A job container changes where most run steps execute. A Docker container action packages one action implementation and runs that action container as a sibling on the job network. These are different reuse boundaries: the job container standardizes the job environment; a container action standardizes one reusable operation.

Do not use a container action merely to hide a complicated shell step from review. If the operation becomes shared automation, pin its action revision and treat its Dockerfile/base image as supply-chain code.

4. Host Docker build versus containerized/Buildx build

For a single-platform local integration image, the hosted Ubuntu runner's Docker CLI is sufficient and keeps the dependency surface small. Buildx becomes valuable for multi-platform output, advanced cache exporters, attestations and more controlled builders. That extra power introduces another builder object, driver, version and evidence surface.

As of the chapter verification date, Docker's official Actions lines include setup-buildx-action v4.1.0 and build-push-action v7.3.0. Production references should pin full commits, not only mutable tags:

# Optional production-style references; not required by the mandatory lab
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
- uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a      # v7.3.0
  with:
    context: .
    push: false

5. Tag versus digest

Reference Use when Trade-off
Version-family tag beginner/disposable lab where portability matters and resolved identity is recorded convenient but tag can move
Patch tag you want a narrower compatibility family still movable unless registry enforces immutability
Manifest digest production build needs immutable base/service input harder to read/update; architecture/manifest-list semantics must be understood
Local image ID you need to prove which image was built in this daemon not globally retrievable and not a registry publication identity

6. Registry authentication is a separate trust boundary

Building locally does not require registry write credentials. Add authentication only when a pull or push actually needs it. Use narrowly scoped tokens, OIDC/federation where the registry/provider supports it, or GitHub-provided tokens with the minimum package permissions. Never bake credentials into image layers or pass them as Docker build arguments when BuildKit secret mounts or provider-native authentication is appropriate.

If a Docker login action is used, the current verified release is docker/login-action v4.6.0. Pin it by full commit in production:

# Optional only; requires an authorized registry and secret
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
  with:
    registry: registry.example.test
    username: ${{ vars.REGISTRY_USER }}
    password: ${{ secrets.REGISTRY_TOKEN }}

7. Integration test is not deployment

A locally built image that passes tests has not been published, approved for an environment or verified healthy in an external target. Registry push, provenance/attestation, environment review and production deployment are later state transitions. Do not collapse them into “Docker build succeeded.”

This distinction becomes important in later chapters on environments, OIDC, attestations and cloud deployment. For now, the lab stops before registry mutation.

8. Worked design scenario

Requirement Recommended design Prerequisites Observable proof
Unit tests need Redis protocol only Redis service container + host job Ubuntu/Linux Docker support health + localhost mapped-port interaction
All scripts require Debian/Python 3.13 userspace Python job container + Redis service Ubuntu runner; compatible job image runtime version + service hostname interaction
One amd64 image for local integration only host Docker build, no push Docker on Ubuntu runner Dockerfile/context + IID + container self-test
Multi-platform release image Buildx + pinned Docker actions + registry registry auth, platform strategy, release governance builder version + per-platform digest + registry digest
Production database migration not a service-container substitute environment approval, external credential/identity, rollback deployment record + external DB state + migration evidence

9. Cost, latency and failure isolation

More service containers increase startup/pull time and consume runner CPU/memory. Very heavy integration stacks may justify a dedicated external environment, but that environment also adds queueing, cleanup and billing state. Measure the bottleneck before turning a two-container test into a long-lived platform.

Keep failure domains narrow. If a Redis contract can be tested with one service container, do not require Kubernetes, a cloud VPC and registry push just to obtain “realism.” Add infrastructure only when it tests an invariant that the simpler topology cannot represent.

10. Production container rules

Record exact source SHA, workflow revision, runner type, Docker/build tool versions, base/service image identities, build context, health checks and test result. Pin third-party actions by full SHA. Prefer image digests for production inputs. Keep write credentials out of PR builds. Avoid privileged mode and host Docker-socket sharing unless there is a documented, isolated requirement.

Knowledge check

When is a service container better than a shared external database?

What does a job container standardize?

Why introduce Buildx only when needed?

What must happen before a local image becomes a deployment artifact?

Why pin Docker-maintained actions by SHA?

Next lesson

Diagnose topology before changing application code

Lesson 4 engineers realistic failures and preserves the original run so repair does not erase the cause.

Official references and version notes

Version and compatibility note

Version-sensitive behavior was rechecked on 2026-09-09. GitHub job containers, service containers and Docker container actions require a Linux runner; on GitHub-hosted runners that means Ubuntu. When a job runs on the host, mapped service ports are reached through localhost; when the job itself runs in a container, service containers share a Docker network and are reached by their service labels without host-port publication. The mandatory labs target ubuntu-24.04, use public version-family images redis:7.4-alpine and python:3.13-slim, and record resolved image metadata rather than claiming those tags are immutable. They use the Docker CLI already present on the hosted Ubuntu runner and perform no registry login or push. Optional production Buildx examples should pin the full SHAs listed above. The optional Docker action references are included for supply-chain design discussion only; the mandatory lab uses no Marketplace action and no registry credential.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.