What Skaffold Does
The Dev Loop
Traditional Kubernetes development has a painful inner loop: change code → run docker build → docker tag → docker push → kubectl apply → wait for rollout → check logs. That's 5–8 manual steps and 1–3 minutes per iteration.
Skaffold collapses this into a single background process. When you save a file, Skaffold detects the change and executes the full pipeline automatically. For static assets (HTML, config files), it can even sync files directly into running containers without rebuilding — sub-second feedback.
flowchart LR
A[Save File] --> B{File sync\neligible?}
B -->|Yes| C[Sync file\ninto container]
B -->|No| D[Rebuild image]
D --> E[Tag with sha256]
E --> F[Push to registry\nor load into Kind]
F --> G[kubectl apply /\nHelm upgrade]
G --> H[Wait for rollout]
H --> I[Stream logs]
C --> I
skaffold dev vs skaffold run
- skaffold dev — Continuous watch mode. Builds, deploys, streams logs, port-forwards, and auto-cleans up on Ctrl+C. Designed for daily development.
- skaffold run — One-shot build + deploy. No watching, no cleanup on exit. Used in CI pipelines.
- skaffold build — Build only (no deploy). Useful to pre-populate a registry.
- skaffold deploy — Deploy only using already-built images. Pair with
skaffold buildin parallel CI stages.
Installation & Init
Install
# macOS
brew install skaffold
# Linux: download binary
curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-linux-amd64
chmod +x skaffold
sudo mv skaffold /usr/local/bin/
# Windows (Chocolatey)
choco install skaffold
# Verify
skaffold version
# v2.13.0
skaffold init
# In a project with a Dockerfile and k8s/ manifests
ls
# Dockerfile k8s/ main.go go.mod
# Auto-generate skaffold.yaml from existing project structure
skaffold init
# Interactive prompts:
# ? Which builders would you like to create Kubernetes resources from?
# [x] Docker (Dockerfile)
# ? Choose the registry to push your image
# Enter image name: myapp
# Creates skaffold.yaml and optionally modifies k8s/deployment.yaml to
# use the image name Skaffold will manage
# Override image if needed
skaffold init --generate-manifests
# For a monorepo with multiple services
skaffold init --compose-file docker-compose.yml
skaffold.yaml Deep Dive
# skaffold.yaml — complete example for a Go microservice
apiVersion: skaffold/v4beta11
kind: Config
metadata:
name: grade-api
# ── BUILD SECTION ─────────────────────────────────────────────
build:
# Default registry for all artifacts
# Override with --default-repo flag or SKAFFOLD_DEFAULT_REPO env var
# tagPolicy defines how images are tagged
tagPolicy:
sha256: {} # tag with content hash (deterministic)
# Alternatives:
# gitCommit: {} # git SHA
# envTemplate:
# template: "{{.IMAGE_TAG}}"
artifacts:
- image: myapp # image name (without registry for local)
context: . # build context directory
docker:
dockerfile: Dockerfile # relative to context
buildArgs: # optional build args
APP_VERSION: "{{.IMAGE_TAG}}"
sync:
# File sync rules — synced directly without rebuild
infer:
- "**/*.html"
- "**/*.css"
- "static/**"
# Second service in a multi-service project
- image: worker
context: ./worker
docker:
dockerfile: Dockerfile.worker
# Local build: push=false means images stay in local daemon / kind cache
local:
push: false
useBuildkit: true # faster builds with BuildKit
concurrency: 0 # 0 = use all CPU cores
# ── DEPLOY SECTION ────────────────────────────────────────────
deploy:
kubectl:
defaultNamespace: default
flags:
apply:
- "--server-side"
- "--field-manager=skaffold"
# ── MANIFESTS ─────────────────────────────────────────────────
manifests:
rawYaml:
- k8s/deployment.yaml
- k8s/service.yaml
- k8s/ingress.yaml
# Or use a glob
# rawYaml:
# - k8s/*.yaml
build Section in Detail
# Multi-artifact build with per-artifact settings
build:
artifacts:
- image: frontend
context: ./frontend
docker:
dockerfile: Dockerfile
cacheFrom:
- frontend:latest # seed layer cache from previous build
sync:
manual:
- src: "src/static/**"
dest: /app/static # path inside the container
- image: backend
context: ./backend
docker:
dockerfile: Dockerfile
# No sync — full rebuild needed for Go binary changes
tagPolicy:
inputDigest: {} # hash of all build inputs (content-addressed)
deploy Section in Detail
# Deploy options: kubectl (raw YAML) or helm (Helm chart) or kustomize
deploy:
# Option 1: Raw kubectl apply
kubectl: {} # uses default settings (namespace from kubeconfig)
# Option 2: Kustomize overlays
# kustomize:
# paths:
# - k8s/overlays/dev
# Option 3: Helm (covered in Part 2)
# helm:
# releases:
# - name: grade-api
# chartPath: charts/grade-api
# valuesFiles:
# - values-dev.yaml
# Lifecycle hooks — run scripts before/after deploy
lifecycleHooks:
before:
- command: ["./scripts/pre-deploy.sh"]
os: [linux, darwin]
after:
- command: ["./scripts/post-deploy.sh"]
os: [linux, darwin]
manifests Section
# manifests can reference rawYaml, kustomize, or helm
manifests:
rawYaml:
- k8s/base/deployment.yaml
- k8s/base/service.yaml
# kustomize:
# paths:
# - k8s/overlays/dev
# hooks:
# before:
# - host:
# command: ["./gen-manifests.sh"]
skaffold dev
# Start the dev loop (blocks, Ctrl+C to stop and clean up)
skaffold dev
# Output (truncated):
# Generating tags...
# - myapp -> myapp:sha256-abc123
# Checking cache...
# - myapp: Not found. Building
# Building [myapp]...
# [myapp] Sending build context to Docker daemon 15.3MB
# ...
# Tags used in deployment:
# - myapp -> myapp:sha256-abc123
# Starting deploy...
# Waiting for deployments to stabilize...
# - deployment/myapp is ready.
# Press Ctrl+C to exit
# Now edit main.go → Skaffold detects change, rebuilds, redeploys automatically
# Edit static/index.html → Skaffold syncs the file into the container directly (no rebuild)
Automatic Port Forwarding
# Add portForward to skaffold.yaml for automatic port exposure during dev
portForward:
- resourceType: deployment
resourceName: grade-api
port: 8080 # container port
localPort: 4503 # host port (optional — defaults to container port)
namespace: default
- resourceType: service
resourceName: grade-api-svc
port: 80
localPort: 8080
- resourceType: pod
resourceName: postgres
port: 5432
localPort: 5432
# Or use the flag directly
skaffold dev --port-forward
# Port forwards are shown in the output:
# Port forwarding deployment/grade-api in namespace default,
# remote port 8080 -> http://127.0.0.1:4503
# Access your app on localhost
curl http://localhost:4503/api/health
File Sync Configuration
# Three types of sync in skaffold.yaml artifacts section:
# 1. infer: Skaffold infers destinations from Dockerfile COPY instructions
sync:
infer:
- "**/*.html"
- "**/*.css"
- "**/*.js"
# 2. manual: Explicit src → dest mapping
sync:
manual:
- src: "config/*.yaml"
dest: /app/config # inside the running container
- src: "templates/**"
dest: /app/templates
strip: "templates/" # strip prefix from src when building dest path
# 3. auto: For JVM / Python / Node hot-reloading (requires compatible runner image)
sync:
auto: true # language-specific: jib images, ko images, etc.
File sync vs rebuild: File sync copies files into a running container using kubectl cp. It only works for changes that don't require a binary recompilation — static files, templates, config files. For Go/Java/Rust code changes, a full rebuild is always needed. Sync is most powerful for Node.js and Python services where the interpreter picks up file changes at runtime.
Build Strategies
# Strategy 1: Docker (default) — local or remote daemon
build:
artifacts:
- image: myapp
docker:
dockerfile: Dockerfile
buildArgs:
VERSION: "{{.IMAGE_TAG}}"
local:
push: false
useBuildkit: true
# ---
# Strategy 2: Kaniko — builds inside Kubernetes (no Docker daemon on CI)
build:
artifacts:
- image: gcr.io/myproject/myapp
kaniko:
dockerfile: Dockerfile
buildArgs:
VERSION: "{{.IMAGE_TAG}}"
cache:
repo: gcr.io/myproject/myapp/cache
cluster:
pullSecretName: kaniko-secret # GCP/AWS/Azure service account
namespace: kaniko
# ---
# Strategy 3: Buildpacks — no Dockerfile needed (auto-detects language)
build:
artifacts:
- image: myapp
buildpacks:
builder: paketobuildpacks/builder:base
env:
- BP_GO_VERSION=1.22
# ---
# Strategy 4: ko — builds Go binaries into minimal images (no Dockerfile)
build:
artifacts:
- image: myapp
ko:
main: . # main package path
flags:
- -ldflags=-s -w # strip debug info
# Run with Kaniko (cluster build)
skaffold dev --profile=ci-kaniko
# Run with Buildpacks
skaffold dev --profile=buildpacks
# Check build strategy is working
skaffold build -v info 2>&1 | head -30
Exercises
Exercise 1 — First skaffold dev: Take any service with a Dockerfile and Kubernetes manifests. Run skaffold init to generate skaffold.yaml. Start skaffold dev --port-forward. Make a code change, save, and observe Skaffold rebuild and redeploy automatically. Measure the time from save to deployment completion.
Exercise 2 — File Sync: Add a sync.infer rule to your skaffold.yaml for HTML templates (templates/**/*.html). In skaffold dev, edit an HTML file and observe that Skaffold syncs the file instead of rebuilding. Compare timing: sync should be near-instant; a rebuild takes 10–30+ seconds.
Exercise 3 — Minikube Integration: Point kubectl at a Minikube cluster. In your skaffold.yaml, set build.local.push: false. Run skaffold dev. Verify that Skaffold detects Minikube and builds images directly into Minikube's Docker daemon (check with eval $(minikube docker-env) && docker images | grep myapp).
Key Takeaways
Key Takeaways:
- Skaffold eliminates manual build-tag-push-apply steps by watching source files and running the full pipeline automatically
skaffold dev is for development (watch + redeploy + logs + cleanup); skaffold run is for CI (one-shot)
- The
skaffold.yaml has three key sections: build (how to build images), deploy (kubectl/helm/kustomize), and manifests (which files to apply)
- File sync (
sync.infer or sync.manual) bypasses rebuilds for static files — essential for fast frontend feedback
- Build strategies: Docker (default), Kaniko (cluster builds for CI), Buildpacks (no Dockerfile), ko (Go-optimized)
- Skaffold auto-detects Minikube and Kind clusters and adjusts image loading accordingly — no extra configuration needed
Next in This Track
In Part 2: Pipelines & Profiles, we configure Skaffold profiles to support local development, staging, and CI pipelines from the same skaffold.yaml, and integrate Helm and Kustomize deployers.