10x Skaffold Build Times

June, 2025
A solution for root Docker dependencies

The Problem: COPY . .

I’ll outline the problem using a standard go project layout, but any project building multiple Docker images—each with root dependencies—will benefit from this approach.

├── cmd/
│   ├── app1/
│   │   └── main.go
│   └── app2/
│       └── main.go
├── pkg/
│   └── utils.go
├── internal/
│   └── helper.go
├── kustomize/
│   ├── base/
│   │   ├── deployment.yaml
│   │   ├── service.yaml
│   │   └── kustomization.yaml
│   └── overlays/
│       └── dev/
│           └── kustomization.yaml
├── go.mod
├── go.sum
├── Dockerfile.app.buildkit
├── .dockerignore
├── skaffold.yaml
├── README.md

Example Dockerfile:

# Dockerfile.app.buildkit
# syntax=docker/dockerfile:1.7

FROM golang:1.22 AS builder

WORKDIR /src

# Leverage BuildKit cache for Go modules
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download

COPY . .

ARG APP_NAME

# Use BuildKit cache for build artifacts as well (optional)
RUN --mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=linux go build -o /app /src/cmd/${APP_NAME}

FROM gcr.io/distroless/base-debian12

COPY --from=builder /app /app

ENTRYPOINT ["/app"]

And build…

DOCKER_BUILDKIT=1 docker build --build-arg APP_NAME=app1 -t my-app1 .

The source of the problem is this line: COPY . .. Because the project root is copied into the Docker build, any change to the project not explicitly ignored by .dockerignore will result in all Docker containers rebuilding (and you waiting, waiting, waiting…).

Solution: FROM $BASE AS builder

The solution is relatively simple—add more Docker.

The BASE image will download all dependencies and only update when they change.

# Dockerfile.base.buildkit
# syntax=docker/dockerfile:1.7

FROM golang:1.22 AS builder

WORKDIR /src

# Leverage BuildKit cache for Go modules
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download

And the updated app Dockerfile:

# Dockerfile.app.buildkit
# syntax=docker/dockerfile:1.7

ARG BASE
FROM $BASE AS builder

WORKDIR /src

ARG APP_NAME

# Use ARGs or multiple Dockerfiles to copy only the required folders to build
your app
COPY cmd cmd
COPY pkg pkg
COPY internal internal

# Use BuildKit cache for build artifacts as well (optional)
RUN --mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=linux go build -o /app /src/cmd/${APP_NAME}

FROM gcr.io/distroless/base-debian12

COPY --from=builder /app /app

ENTRYPOINT ["/app"]

If you’re familiar with Skaffold, you may have wondered how the dependencies image will be referenced, since Skaffold will build and update the image reference on each change. The trick is to explicitly pass the BASE image as a required dependency in skaffold.yaml.

# skaffold.yaml
build:
artifacts:
# Build the 'base' dependencies image
    - image: base
    docker:
    dockerfile: Dockerfile.base.buildkit
    # Reference BASE in other image builds
    - image: app1
    docker:
    dockerfile: Dockerfile.app.buildkit
    buildArgs:
    APP_NAME: app1
    requires:
        - image: base
        alias: BASE
    - image: app2
    docker:
    dockerfile: Dockerfile.app.buildkit
    buildArgs:
    APP_NAME: app2
    requires:
        - image: base
        alias: BASE

Skaffold will ensure the base image is built first and pass it as the BASE build argument to your app’s Dockerfile.

Pro Tips

  • .dockerignore: Make sure your .dockerignore is set up to exclude files and directories that aren’t needed for dependency resolution (like bin/, vendor/, or node_modules/). This keeps your build context small and leverages Docker’s cache more effectively.

  • --build-concurrency=0: Enables full concurrency.

  • --trigger=manual: Will not rebuild until triggered.

  • --cleanup=false: Keeps k8s resources even when the build fails (useful for debugging).

    Altogether: skaffold dev --cleanup=false --trigger=manual --build-concurrency=0

Conclusion

By introducing a shared base image for your dependencies, you can significantly reduce redundant work in your Skaffold builds. This technique is simple to implement and pays off immediately in faster, more efficient development cycles—especially as your project scales.