Skaffold for Kubernetes Development Workflow

Skaffold automates the build-push-deploy cycle for Kubernetes applications, enabling continuous development with hot reload, multi-stage pipelines, and seamless CI/CD integration. This guide covers installing Skaffold on Linux, configuring build and deploy pipelines, using profiles for different environments, and integrating with Helm.

Prerequisites

  • Docker or a container runtime installed
  • kubectl configured with a Kubernetes cluster
  • A Kubernetes cluster (local: kind, Minikube, or Docker Desktop)
  • A container registry (Docker Hub, GCR, ECR, or a local registry)

Install Skaffold

# Linux (amd64)
curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-linux-amd64
chmod +x skaffold
sudo mv skaffold /usr/local/bin/

# Verify installation
skaffold version

# macOS
brew install skaffold

Initialize a Skaffold Project

# From your project root (must have a Dockerfile and Kubernetes manifests)
skaffold init

# Skaffold detects Dockerfiles, Helm charts, Kustomize dirs and prompts:
# [1] Dockerfile
# Which builders would you like to create kubernetes resources from?
# > Dockerfile (myapp/Dockerfile)
# Enter a valid Kubernetes namespace: default

# This creates skaffold.yaml in the project root

A generated skaffold.yaml looks like:

apiVersion: skaffold/v4beta6
kind: Config
metadata:
  name: my-app
build:
  artifacts:
    - image: my-org/web-app
      context: .
      docker:
        dockerfile: Dockerfile
deploy:
  kubectl:
    manifests:
      - k8s/*.yaml

Configure Build Pipelines

Docker build (default):

# skaffold.yaml
apiVersion: skaffold/v4beta6
kind: Config

build:
  artifacts:
    - image: my-org/web-app
      context: .
      docker:
        dockerfile: Dockerfile
        target: production        # Multi-stage build target
        buildArgs:
          NODE_ENV: production
    - image: my-org/worker
      context: ./worker
      docker:
        dockerfile: Dockerfile
  local:
    push: false                   # Don't push when using local cluster
    useBuildkit: true

Kaniko build (for CI environments without Docker daemon):

build:
  artifacts:
    - image: my-org/web-app
      kaniko:
        dockerfile: Dockerfile
        buildArgs:
          VERSION: "1.0.0"
  cluster:
    namespace: kaniko
    pullSecretPath: /tmp/kaniko-secret.json

ko build (for Go projects — no Dockerfile needed):

build:
  artifacts:
    - image: my-org/go-app
      ko:
        fromImage: gcr.io/distroless/static-debian11
        main: ./cmd/server
        env:
          - GOOS=linux
          - GOARCH=amd64

Buildpacks (auto-detect language and build):

build:
  artifacts:
    - image: my-org/app
      buildpacks:
        builder: gcr.io/buildpacks/builder:v1

Deploy with Helm or Kustomize

Deploy with kubectl:

deploy:
  kubectl:
    manifests:
      - k8s/base/*.yaml
      - k8s/overlays/development/*.yaml
    defaultNamespace: development

Deploy with Helm:

deploy:
  helm:
    releases:
      - name: web-app
        chartPath: helm/web-app
        namespace: development
        createNamespace: true
        valuesFiles:
          - helm/values.yaml
          - helm/values-dev.yaml
        setValues:
          image.repository: my-org/web-app
          image.pullPolicy: IfNotPresent
        upgradeOnChange: true

Deploy with Kustomize:

deploy:
  kustomize:
    paths:
      - k8s/overlays/development

File Sync and Hot Reload

Skaffold can sync files directly into running containers without a rebuild:

build:
  artifacts:
    - image: my-org/web-app
      context: .
      docker:
        dockerfile: Dockerfile
      sync:
        # Infer sync from .dockerignore (fastest)
        infer:
          - "**/*.js"
          - "**/*.css"
          - "**/*.html"
        # Manual sync: local path -> container path
        manual:
          - src: "src/**/*.py"
            dest: /app/src/

Development mode with auto-rebuild:

# Start the development loop
skaffold dev

# Skaffold will:
# 1. Build all artifacts
# 2. Deploy to the cluster
# 3. Watch for file changes
# 4. Re-build and re-deploy on changes (or sync files if configured)
# 5. Stream logs from all pods
# 6. Clean up on Ctrl+C

# Port-forward automatically
skaffold dev --port-forward

# Tail logs while developing
skaffold dev --tail

Skaffold Profiles

Profiles let you customize Skaffold behavior per environment:

# skaffold.yaml
apiVersion: skaffold/v4beta6
kind: Config

build:
  artifacts:
    - image: my-org/web-app
      docker:
        dockerfile: Dockerfile

deploy:
  kubectl:
    manifests:
      - k8s/base/*.yaml

# Profile for CI (use Kaniko, push to registry)
profiles:
  - name: ci
    build:
      artifacts:
        - image: my-org/web-app
          kaniko:
            dockerfile: Dockerfile
      cluster:
        namespace: kaniko
    deploy:
      kubectl:
        manifests:
          - k8s/overlays/staging/*.yaml

  # Profile for production deployment
  - name: production
    build:
      artifacts:
        - image: my-org/web-app
          docker:
            dockerfile: Dockerfile
            target: production
      tagPolicy:
        gitCommit: {}       # Tag with git commit SHA
    deploy:
      helm:
        releases:
          - name: web-app
            chartPath: helm/web-app
            valuesFiles:
              - helm/values-prod.yaml
# Run with a specific profile
skaffold dev --profile=ci
skaffold run --profile=production

# Activate multiple profiles
skaffold run -p ci -p feature-flag-x

CI/CD Integration

GitHub Actions:

# .github/workflows/deploy.yml
name: Build and Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Skaffold
        run: |
          curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-linux-amd64
          chmod +x skaffold && sudo mv skaffold /usr/local/bin/

      - name: Configure Docker
        run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin

      - name: Configure kubectl
        run: echo "${{ secrets.KUBECONFIG }}" | base64 -d > /tmp/kubeconfig

      - name: Deploy with Skaffold
        env:
          KUBECONFIG: /tmp/kubeconfig
        run: skaffold run --profile=ci --tag=${{ github.sha }}

One-shot build and push (no deploy):

# Build and push images only
skaffold build --push --tag=$(git rev-parse --short HEAD)

# Render manifests to stdout (useful for GitOps)
skaffold render --profile=production --output=rendered.yaml
kubectl apply -f rendered.yaml

Troubleshooting

Image build fails:

# Enable verbose output
skaffold dev -v debug

# Test the Docker build directly
docker build -t my-org/web-app:test .

# Check that Docker daemon is running
docker info

Pods not starting after deploy:

# Check pod status
kubectl get pods -n development

# Skaffold streams logs automatically in dev mode
# For run mode:
kubectl logs -l app=web-app -n development --tail=50

File sync not working:

# Verify sync config in skaffold.yaml
skaffold dev -v debug 2>&1 | grep -i "sync"

# File sync requires the container to be running and the path to exist
# Check the container filesystem
kubectl exec -it <pod-name> -- ls /app/src/

Port forwarding fails:

# List available services for port-forwarding
kubectl get services -n development

# Manual port forward as a workaround
kubectl port-forward svc/web-app 8080:80 -n development

Conclusion

Skaffold dramatically reduces the friction of Kubernetes development by automating the build-deploy loop and providing file sync for rapid iteration. Its profile system handles the full lifecycle from local development through CI and production deployment, with support for Docker, Kaniko, Buildpacks, Helm, and Kustomize. Integrating Skaffold into your CI/CD pipeline ensures developers and automation use the same deployment tooling.