k8s
How to write skaffold.yaml — preview/staging/production profiles, Kind setup, Cloud Run patterns. Use when setting up deployment pipelines, K8s previews, staging, or production deploys.
Files1
View on GitHubSKILL.mdplugins/sayt/skills/k8s/SKILL.md
GitHubrelease / verify — Publishing and Skaffold Deploys
sayt release makes the work public — typically goreleaser publishing versioned artifacts, with skaffold build --push as the mechanism for building and pushing container images. sayt verify is a no-op by default; wire it in .say.yaml to run skaffold verify (or any other post-release check) when the project needs it.
For deploys to preview / staging / production, use skaffold dev / skaffold run directly. sayt has no wrapper verb for deploys — skaffold is already a verb runner.
sayt release — Making Work Public
"Release" means make the work public. What that means depends on the project:
- Library — publish a package (npm, crates.io, PyPI, Maven Central) via goreleaser.
- CLI tool — cut a tagged release with multi-platform binaries via goreleaser.
- Server (container-based) — publish versioned container images to the cloud registry. The typical pattern is goreleaser delegating to
skaffold build --pushso image naming, platforms, and tags match the deploy pipeline. - Deploy-on-release — for projects where "make it public" means a live deploy,
releasemay invokeskaffold run -p productiondirectly, or goreleaser may publish images and a separate step promotes them.
sayt release runs release.nu, which:
- Computes the next version via
git-cliff(or accepts--version <v>to bypass). - Validates against the
VERSIONfile if present — aborts if they disagree, so you fix and re-runsayt lint. - Creates the git tag.
- Runs
goreleaser releasewith the computed version exported asGORELEASER_CURRENT_TAG.
--snapshot builds only, no tag or push.
.goreleaser.yaml — Container Service via Skaffold
Most services in this monorepo use this shape: goreleaser owns versioning and changelog; skaffold owns image building and pushing.
version: 2
project_name: services-tracker
before:
hooks:
- skaffold build -p production --push=false --tag={{ .Version }}
builds:
- builder: zig
skip: true
publishers:
- name: skaffold
cmd: skaffold build -p production --tag={{ .Version }}
release:
disable: true
Why the stubbed builds: — goreleaser's native builders can't drive docker buildx for multi-arch images the way skaffold already does. The real work happens in publishers:. The GitHub release is disabled because the artifact is the image, not a tarball.
.goreleaser.yaml — Binary Release
CLI tools (sayt itself, etc.) use goreleaser's native multi-platform build flow with builds:, archives:, and GitHub release enabled. No skaffold involvement.
When adding release to a new service, look at a neighboring .goreleaser.yaml first — match the surrounding convention.
sayt verify — Post-Deploy Validation
verify.nu does nothing: its main accepts any args and returns. The verb exists so every project has one name for "check what was released", and the project fills it in — a do: under say.verify in .say.yaml, a .sayt.verify.nu script beside it (sayt's own fetches the published wrapper, installs it into a temp dir and smoke-tests the binary), or a rulemap split by @platform. For a Kubernetes service, do: "skaffold verify -p production" runs the verification containers declared in skaffold.yaml's verify: section against the deployed environment.
Deploys Use Skaffold Directly
# Preview — local Kind cluster
kind create cluster -n iris # one-time
skaffold dev -p preview # watch mode
skaffold run -p preview # one-shot
# Staging — shared cloud deployment, followed by CI/CD
skaffold run -p staging
# Production — manually approved promotion from staging
skaffold run -p production
Kind clusters are ephemeral — kind delete cluster -n iris && kind create cluster -n iris anytime.
Preview is isolated with mocked externals, no outbound internet except vendored resources, production-like builds. Staging is built hermetically by CI/CD, pushed to GCP by Skaffold (typically Cloud Build + Cloud Run), follows main branch. Production is the same image as staging with different config; Crossplane manages infrastructure (Cloud Run, Cloud SQL, IAM, buckets).
skaffold.yaml Shape
apiVersion: skaffold/v4beta11
kind: Config
metadata:
name: my-service
build:
artifacts:
- image: my-service
docker: { dockerfile: Dockerfile }
profiles:
- name: preview
build:
local: { push: false } # Kind doesn't need a registry push
deploy:
kubectl:
manifests: [k8s/preview/*.yaml]
- name: staging
build:
googleCloudBuild: { projectId: my-project }
deploy:
cloudrun:
projectid: my-project
region: us-central1
- name: production
deploy:
cloudrun:
projectid: my-project-prod
region: us-central1
Writing Good Skaffold Configs
- Profiles for preview, staging, production as a minimum.
- Preview builds locally with
push: false— Kind doesn't need a registry. - Staging and production use cloud build (GCB or similar) for hermetic builds.
- Production is a promotion of staging — same image, different config.
- Separate manifest dirs per env —
k8s/preview/,k8s/staging/,k8s/production/.
Current flags
Run sayt help release and sayt help verify for current flags.