Skaffold doesn’t just run your build, push, and deploy steps; it actively orchestrates them, managing the lifecycle of your application in Kubernetes with a single command.

Let’s see it in action. Imagine you have a simple Node.js app:

// server.js
const express = require('express');
const app = express();
const port = 8080;

app.get('/', (req, res) => {
  res.send('Hello from Skaffold!');
});

app.listen(port, () => {
  console.log(`App listening on port ${port}`);
});

And a Dockerfile:

FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 8080
CMD ["node", "server.js"]

Your skaffold.yaml might look like this:

apiVersion: skaffold.dev/v2beta10
kind: Config
build:
  local:
    push: false # We'll push later
  artifacts:
    - image: my-node-app
      docker:
        dockerfile: Dockerfile
deploy:
  kubectl:
    manifests:
      - k8s/deployment.yaml

And a basic Kubernetes deployment:

# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-node-app-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-node-app
  template:
    metadata:
      labels:
        app: my-node-app
    spec:
      containers:
        - name: my-node-app
          image: my-node-app # Skaffold will tag this
          ports:
            - containerPort: 8080

When you run skaffold dev, Skaffold kicks off the process. It first looks at the build section. It sees local and artifacts. For my-node-app, it finds the Dockerfile. It builds the image, tags it with a unique hash (e.g., my-node-app:a1b2c3d4), and since push is false in the local builder, it leaves it locally available.

Next, it moves to deploy. It finds kubectl and manifests. It reads k8s/deployment.yaml. Crucially, it substitutes the image field (my-node-app) with the fully tagged image it just built (my-node-app:a1b2c3d4). It then applies this modified manifest to your Kubernetes cluster using kubectl apply.

The "dev" command in skaffold dev means it then watches for changes. If you modify server.js or Dockerfile, Skaffold detects this, rebuilds the image with a new tag (e.g., my-node-app:e5f6g7h8), updates the Kubernetes manifest with this new tag, and redeploys. This rapid iteration loop is the core value.

The push action is handled implicitly or explicitly. If you were using a remote builder like docker-daemon or google-cloud-build, Skaffold would push the image after building. For local builds, you’d typically run skaffold build which would also push the image to your configured registry (defined in skaffold.yaml or via environment variables), and then skaffold deploy would use that pushed image. The skaffold dev command integrates these for a seamless workflow.

Skaffold manages the entire lifecycle. It knows how to build your container image(s), tag them uniquely (usually with Git commit SHA or a timestamped hash), push them to a registry (if configured), and then update your Kubernetes manifests with these new image tags before applying them. This ensures that your deployed application always reflects the latest code you’ve committed. The magic is in the automatic image tagging and manifest substitution, which removes the manual docker build, docker push, sed (or equivalent), and kubectl apply steps.

When Skaffold performs a kubectl apply, it injects a unique label into the deployed resources (like Deployments or StatefulSets). This label is typically skaffold.dev/run-id and it’s tied to the specific invocation of skaffold dev. This allows Skaffold to know exactly which resources it deployed during that session, enabling it to perform cleanup operations (like skaffold delete) or to identify and manage rolling updates effectively. If you’ve ever seen a deployment update where old pods are terminated and new ones are created, this label is part of how Skaffold tracks and manages that transition.

The next step is understanding how Skaffold handles complex multi-stage builds and different deployment strategies beyond basic kubectl apply.

Want structured learning?

Take the full Skaffold course →