Skaffold’s pipeline caching can make repeated builds feel almost instantaneous, but its true power lies in how it fundamentally changes the state Skaffold tracks.

Let’s watch Skaffold build a simple Go application and deploy it to Kubernetes.

apiVersion: v1
kind: Pod
metadata:
  name: go-app
spec:
  containers:
  - name: go-app
    image: my-registry/go-app:latest
    ports:
    - containerPort: 8080
package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Hello from Go!")
	})
	fmt.Println("Server starting on port 8080...")
	http.ListenAndServe(":8080", nil)
}
# First build and deploy
skaffold dev

# ... Skaffold builds the image, pushes it, and deploys the pod ...

# Make a trivial change to the Go app (e.g., add a space)
# Re-run skaffold dev

# ... Skaffold rebuilds, repushes, and redeploys ...

Now, imagine we have a complex multi-stage Dockerfile, or we’re building a large binary. The second build, even with a tiny change, will still take a significant amount of time. This is where Skaffold’s caching comes into play.

Skaffold doesn’t just cache the final artifact. It caches intermediate build steps defined within your build configuration. When you run skaffold dev or skaffold build, Skaffold analyzes your build process. For Docker, it inspects your Dockerfile. For Jib, it understands the Java build process.

Here’s how it works under the hood:

  1. Dependency Hashing: Skaffold hashes the inputs to each build step. For a Dockerfile, this includes the Dockerfile itself, any files copied into the image (like your application code), and even the base image. For Jib, it hashes your source code, dependencies, and build configuration.
  2. Cache Lookup: Skaffold maintains a cache, typically stored in ~/.skaffold/cache. It checks if a previous build step with the exact same input hash has already been performed.
  3. Cache Hit/Miss:
    • Cache Hit: If a matching hash is found, Skaffold reuses the result of that cached step. For Docker, this means using the layer from the Docker build cache. For Jib, it means reusing pre-built artifacts. This bypasses actual build execution for that step.
    • Cache Miss: If no matching hash is found, Skaffold executes the build step and stores its result in the cache for future use.

The crucial part is that Skaffold is smart about what it caches. It doesn’t just cache the final image. It caches the result of each distinct build operation. This means if you have a multi-stage Dockerfile, and only the last stage changes, Skaffold can reuse the results of all the earlier stages.

Consider this skaffold.yaml:

apiVersion: skaffold/v2
kind: Config
build:
  artifacts:
    - image: my-registry/go-app
      docker:
        dockerfile: Dockerfile
  local:
    push: false
deploy:
  kubectl:
    manifests:
      - k8s/*.yaml

If you change a line of code in your main.go, Skaffold will:

  1. Hash main.go and see it’s changed.
  2. Recognize that the docker build command depends on main.go (via COPY . . in the Dockerfile).
  3. Trigger a rebuild because the input hash for that docker build step is now different.
  4. The docker build command itself will leverage Docker’s layer caching, but Skaffold ensures the correct Docker build is run based on the changed source.

The real magic happens when your build process is more involved. Suppose you have a Dockerfile like this:

# Stage 1: Build frontend assets
FROM node:18 as frontend
WORKDIR /app
COPY frontend/package.json frontend/package-lock.json ./
RUN npm ci
COPY frontend/ .
RUN npm run build

# Stage 2: Build backend
FROM golang:1.20 as backend
WORKDIR /app
COPY main.go go.mod go.sum ./
RUN go mod download
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/server main.go

# Stage 3: Combine
FROM alpine:latest
COPY --from=frontend /app/dist /app/static
COPY --from=backend /app/server /app/server
EXPOSE 8080
CMD ["/app/server"]

If you only change main.go, Skaffold (and Docker’s layer caching, which Skaffold leverages) will rebuild the backend stage, but can reuse the frontend build stage if its inputs haven’t changed. Skaffold’s caching ensures that if the entire build block’s inputs (Dockerfile, source code, etc.) haven’t changed, it won’t even attempt a build.

The "pipeline caching" is really about Skaffold orchestrating and understanding the cacheable units within your build process, whether that’s Docker layers, Jib’s build steps, or other builders. It makes the difference between waiting minutes for a rebuild and seconds.

The next thing you’ll want to explore is how Skaffold handles different cache backends beyond the local filesystem, like remote caching for team collaboration.

Want structured learning?

Take the full Skaffold course →