Stage 7 · Master
Phase 2 — Organization Service
Dockerization
Package the organization service into a container image — and hit the one build-context mistake that go.work's multi-module layout makes almost inevitable on a first attempt.
The Obvious docker build Context Is the Wrong One
The instinctive command is docker build -t organization services/organization, treating the service's own directory as both the build context and the Dockerfile location — exactly how a single-module Go service would be built. This service is not single-module: its go.work references sibling module pkg/apperr, pkg/config, and pkg/logging by relative path, and Docker's build context can never reach outside itself to resolve a COPY of something one directory above it. The Dockerfile below is written for a build context of the repository root, not services/organization.
Reproducing the Wrong-Context Failure Deliberately
=> ERROR [build 2/6] COPY go.work go.work.sum ./
------
> [build 2/6] COPY go.work go.work.sum ./:
------
failed to compute cache key: "/go.work" not found: not found
The error is not a Go compilation failure and not a missing dependency — it is Docker reporting that a file the Dockerfile asked for simply does not exist inside the context it was given, because that context was scoped one directory too deep.
A Multi-Stage Dockerfile Built for a Repository-Root Context
# syntax=docker/dockerfile:1.7
FROM golang:1.26-alpine AS build
WORKDIR /src
COPY pkg ./pkg
COPY services/organization ./services/organization
RUN go work init ./services/organization ./pkg/config ./pkg/logging ./pkg/apperr
WORKDIR /src/services/organization
RUN go build -o /out/organization ./cmd/api
FROM alpine:3.20
RUN adduser -D -u 10001 appuser
COPY --from=build /out/organization /usr/local/bin/organization
USER appuser
EXPOSE 8080
ENTRYPOINT ["/usr/local/bin/organization"]
The image creates a build-specific workspace instead of copying the repository go.work, which references services not present in this build context layer. The final stage contains only the compiled binary and a non-root user.
Buildx, Multi-Arch, and a Real --load Limitation
docker buildx build supports building for multiple architectures in one invocation, but Docker's local image store can only hold one architecture's image at a time per tag — --load fails outright if asked to load more than one platform's result. Building linux/amd64,linux/arm64 together only makes sense when the output is pushed to a registry (--push), which can hold a multi-arch manifest; local testing stays single-platform with --load.
Running the Built Image and Confirming It Actually Serves Traffic
Applied exercise
Measure the layer-cache benefit the Dockerfile's COPY ordering claims to provide
The Dockerfile's comment claims copying go.work before service source improves cache reuse. Measure it rather than accepting the claim.
- Build the image once from a clean state and record the total build time reported by Docker.
- Change one line of Go source inside services/organization/internal (a comment is enough) and rebuild; record how many of the COPY/RUN steps show CACHED versus how many re-run.
- Now reverse the Dockerfile's COPY order (copy services/organization before pkg and go.work) and repeat both builds; compare which ordering re-executes fewer steps for a source-only change.
Deliverable
A short table comparing cache-hit counts and total build time between the original ordering and the reversed ordering, for an application-source-only change.
Completion checks
- The comparison correctly shows the original ordering (go.work first) preserves more cached layers when only application source changes.
- The Dockerfile is restored to its original ordering at the end of the exercise.
Why does docker build -f services/organization/Dockerfile -t organization services/organization fail for this service specifically, when it would work for an ordinary single-module Go service?
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.