
Managing Infra
- 100 installs
- 6 repo stars
- Updated July 22, 2026
- julianobarbosa/claude-code-skills
Best practices for managing infrastructure and operations.
About
Infrastructure patterns for Kubernetes, Terraform, Helm, Kustomize, and GitHub Actions. Use when making K8s architectural decisions.
- CI workflow: Lint, test, compile on PRs
- Release workflow: Multi-arch Docker build on tags
Managing Infra by the numbers
- 100 all-time installs (skills.sh)
- +1 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #814 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/julianobarbosa/claude-code-skills --skill managing-infraAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 100 |
|---|---|
| repo stars | ★ 6 |
| Last updated | July 22, 2026 |
| Repository | julianobarbosa/claude-code-skills ↗ |
What it does
Best practices for managing infrastructure and operations.
Files
Infrastructure Patterns
When to Use What
| Tool | Use For |
|---|---|
| Raw K8s YAML | Simple deployments, one-off resources |
| Kustomize | Environment variations, overlays without templating |
| Helm | Complex apps, third-party charts, heavy templating |
| Terraform | Cloud resources, infrastructure lifecycle |
| GitHub Actions | CI/CD, automated testing, releases |
| Makefile | Build automation, self-documenting targets |
| Dockerfile | Container builds, multi-stage, multi-arch |
Quick Decisions
Kustomize when: Simple env differences, readable manifests, patching YAML Helm when: Complex templating, third-party charts, release management
K8s Security Defaults
Every workload: non-root user, read-only filesystem, no privilege escalation, dropped capabilities, network policies.
GitHub Actions Patterns
- CI workflow: Lint, test, compile on PRs (run on both x86 + ARM)
- Release workflow: Multi-arch Docker build on tags (native ARM runners)
- Pin actions by SHA, least-privilege permissions
References
- KUBERNETES.md - K8s resource patterns
- TERRAFORM.md - Terraform module patterns
- GITHUB-ACTIONS.md - CI/CD workflow patterns
- MAKEFILE.md - Build automation patterns
- DOCKERFILE.md - Container build patterns
- templates/ - Ready-to-use templates
Commands
kubectl apply -k ./ # Apply kustomize
helm upgrade --install NAME . # Install/upgrade chart
terraform plan && terraform apply---
Gotchas
- Terraform state lock contention: default 10-min lock timeout; bumped timeout doesn't help if the lock holder hung — force-unlock only after confirming the process is dead.
- Helm release name reuse on uninstalled-but-not-purged release fails install with "already exists" — use
--no-hooks+ explicit purge, or never reuse names. - Kustomize patches that match nothing silently produce empty diffs — verify with
kustomize buildafter every patch addition. - Terraform `for_each` over a computed value forces apply-time count — can cause spurious re-creation of resources between plans.
- `helm upgrade --install` on a changed values schema can silently drop fields that no longer match — diff the rendered output, not just the values file.
- `kubectl apply --server-side` vs client-side conflicts when both have been used: client-side last-applied-config can shadow server-side managed fields without error.
Dockerfile Patterns
Go Multi-Stage Build
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/bin/server ./cmd/server
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /app/bin/server /server
USER nonroot:nonroot
ENTRYPOINT ["/server"]Python Multi-Stage Build
FROM python:3.14-slim AS builder
WORKDIR /app
RUN pip install uv
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev
FROM python:3.14-slim
WORKDIR /app
COPY --from=builder /app/.venv /app/.venv
COPY src/ ./src/
ENV PATH="/app/.venv/bin:$PATH"
USER nobody:nobody
ENTRYPOINT ["python", "-m", "src.main"]Base Image Selection
| Use Case | Image |
|---|---|
| Go static binary | gcr.io/distroless/static-debian12:nonroot |
| Go with cgo | gcr.io/distroless/base-debian12:nonroot |
| Minimal scratch | scratch |
| Python | python:3.14-slim |
| Debug needed | alpine or debian:bookworm-slim |
Security Best Practices
# Non-root user
USER nonroot:nonroot
# or
USER nobody:nobody
# or specific UID
USER 65532:65532
# Read-only root filesystem (set in K8s or compose)
# No HEALTHCHECK with secrets
# No ADD for remote URLs (use COPY)Multi-Platform Build
# Platform-aware build
FROM --platform=$BUILDPLATFORM golang:1.23-alpine AS builder
ARG TARGETOS TARGETARCH
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /app/serverBuild with:
docker buildx build --platform linux/amd64,linux/arm64 --push -t image:tag .Caching Layers
Order from least to most frequently changed:
1. Base image 2. System dependencies 3. Language dependencies (go.mod, pyproject.toml) 4. Application code
COPY go.mod go.sum ./
RUN go mod download # Cached unless deps change
COPY . . # Invalidates on any code change
RUN go build.dockerignore
.git
.github
*.md
!README.md
Makefile
.env*GitHub Actions Patterns
Workflow Structure
Separate workflows for different purposes:
.github/workflows/
├── ci.yml # PRs: lint, test, compile
├── release.yml # Tags: multi-arch Docker build
└── security.yml # Scheduled: dependency scanningCI Workflow (PRs)
name: CI
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
lint:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- run: golangci-lint run
test:
strategy:
matrix:
os: [ubuntu-24.04, ubuntu-24.04-arm]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- run: go test -race -coverprofile=coverage.out ./...
- uses: actions/upload-artifact@v4
with:
name: coverage-${{ matrix.os }}
path: coverage.outRelease Workflow (Tags)
Multi-arch Docker with native ARM runners (no QEMU):
name: Release
on:
push:
tags: ["v*"]
permissions:
contents: read
packages: write
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-binaries:
strategy:
matrix:
include:
- os: ubuntu-24.04
goos: linux
goarch: amd64
- os: ubuntu-24.04-arm
goos: linux
goarch: arm64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- run: |
CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/app-${{ matrix.goos }}-${{ matrix.goarch }} .
- uses: actions/upload-artifact@v4
with:
name: binary-${{ matrix.goos }}-${{ matrix.goarch }}
path: bin/
docker:
needs: build-binaries
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
pattern: binary-*
path: bin/
merge-multiple: true
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/metadata-action@v5
id: meta
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
- uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=maxPython CI
jobs:
test-python:
strategy:
matrix:
os: [ubuntu-24.04, ubuntu-24.04-arm]
python-version: ["3.13", "3.14"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: ${{ matrix.python-version }}
cache: pip
- run: |
pip install -e ".[dev]"
pytest --covCaching Strategies
Go
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
with:
go-version-file: go.mod
cache: true # Built-in GOMODCACHE cachingPython
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.14"
cache: pip
cache-dependency-path: pyproject.tomlDocker
- uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6
with:
cache-from: type=gha
cache-to: type=gha,mode=maxSecurity Best Practices
Permissions
permissions:
contents: read # Default for most jobs
packages: write # Only for release jobs
id-token: write # Only for OIDC authPin Actions by SHA
# Good - pinned
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
# Bad - floating tag
- uses: actions/checkout@v4Environment Protection
jobs:
deploy:
environment: production # Requires approval
runs-on: ubuntu-24.04Concurrency Control
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: trueMulti-Arch Build (No QEMU)
Use native ARM runners for compilation, combine in final image:
jobs:
build:
strategy:
matrix:
include:
- runner: ubuntu-24.04
platform: linux/amd64
- runner: ubuntu-24.04-arm
platform: linux/arm64
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- run: docker build -t app:${{ matrix.platform }} .
- run: docker save app:${{ matrix.platform }} > image.tar
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: image-${{ matrix.platform }}
path: image.tar
manifest:
needs: build
runs-on: ubuntu-24.04
steps:
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
# Create and push multi-arch manifestReusable Workflows
# .github/workflows/reusable-go-ci.yml
on:
workflow_call:
inputs:
go-version:
type: string
default: "1.25"
jobs:
ci:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
with:
go-version: ${{ inputs.go-version }}
- run: go test ./...Usage:
jobs:
go:
uses: ./.github/workflows/reusable-go-ci.yml
with:
go-version: "1.25"Kubernetes Patterns
Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
labels:
app.kubernetes.io/name: app
app.kubernetes.io/component: backend
spec:
replicas: 3
selector:
matchLabels:
app.kubernetes.io/name: app
template:
metadata:
labels:
app.kubernetes.io/name: app
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
containers:
- name: app
image: app:latest
ports:
- containerPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: [ALL]
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080Service
apiVersion: v1
kind: Service
metadata:
name: app
spec:
selector:
app.kubernetes.io/name: app
ports:
- port: 80
targetPort: 8080
type: ClusterIPConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: info
FEATURE_FLAG: "true"Secret
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
stringData:
DATABASE_URL: postgres://user:pass@host/dbKustomize Structure
base/
├── kustomization.yaml
├── deployment.yaml
├── service.yaml
└── configmap.yaml
overlays/
├── dev/
│ ├── kustomization.yaml
│ └── patch-replicas.yaml
└── prod/
├── kustomization.yaml
└── patch-replicas.yamlbase/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- configmap.yamloverlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namespace: production
patches:
- path: patch-replicas.yamloverlays/prod/patch-replicas.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
replicas: 5Network Policy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: app-network-policy
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: app
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: frontend
ports:
- port: 8080
egress:
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: database
ports:
- port: 5432HPA
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: app
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: app
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70Standard Labels
labels:
app.kubernetes.io/name: myapp
app.kubernetes.io/instance: myapp-prod
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/component: backend
app.kubernetes.io/part-of: myplatform
app.kubernetes.io/managed-by: helmMakefile Patterns
Self-Documenting Help
.PHONY: help
help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | \
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
.DEFAULT_GOAL := helpGo Project
.PHONY: build test lint fmt clean
VERSION ?= $(shell git describe --tags --always --dirty)
LDFLAGS := -ldflags "-X main.version=$(VERSION)"
build: ## Build binary
go build $(LDFLAGS) -o bin/app ./cmd/app
test: ## Run tests
go test -v -race ./...
lint: ## Run linter
golangci-lint run
fmt: ## Format code
go fmt ./...
goimports -w .
clean: ## Clean build artifacts
rm -rf bin/ dist/Python Project
.PHONY: install test lint fmt clean
install: ## Install dependencies
uv sync
test: ## Run tests
uv run pytest -v
lint: ## Run linter
uv run ruff check .
fmt: ## Format code
uv run ruff format .
clean: ## Clean cache files
rm -rf .pytest_cache .ruff_cache __pycache__ .mypy_cacheDocker Targets
.PHONY: docker-build docker-push
IMAGE := ghcr.io/user/app
TAG := $(VERSION)
docker-build: ## Build Docker image
docker build -t $(IMAGE):$(TAG) -t $(IMAGE):latest .
docker-push: ## Push Docker image
docker push $(IMAGE):$(TAG)
docker push $(IMAGE):latestMulti-Platform Build
.PHONY: docker-buildx
docker-buildx: ## Build and push multi-arch image
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag $(IMAGE):$(TAG) \
--tag $(IMAGE):latest \
--push .Phony Declarations
Always declare .PHONY for non-file targets to avoid conflicts with files of the same name.
.PHONY: all build test lint fmt clean install helpVariables
# Conditional defaults
GO ?= go
GOFLAGS ?=
# Shell commands
SHELL := /bin/bash
.SHELLFLAGS := -eu -o pipefail -cname: CI
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
lint:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- uses: golangci/golangci-lint-action@v6
with:
version: latest
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, ubuntu-24.04-arm]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Run tests
run: go test -race -coverprofile=coverage.out ./...
- uses: actions/upload-artifact@v4
with:
name: coverage-${{ matrix.os }}
path: coverage.out
build:
needs: [lint, test]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Build
run: go build -v ./...
apiVersion: apps/v1
kind: Deployment
metadata:
name: APP_NAME
labels:
app.kubernetes.io/name: APP_NAME
app.kubernetes.io/component: backend
spec:
replicas: 3
selector:
matchLabels:
app.kubernetes.io/name: APP_NAME
template:
metadata:
labels:
app.kubernetes.io/name: APP_NAME
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
containers:
- name: APP_NAME
image: IMAGE:TAG
ports:
- containerPort: 8080
env:
- name: LOG_LEVEL
value: info
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: [ALL]
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: APP_NAME
spec:
selector:
app.kubernetes.io/name: APP_NAME
ports:
- port: 80
targetPort: 8080
type: ClusterIP
name: Release
on:
push:
tags: ["v*"]
permissions:
contents: read
packages: write
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-binaries:
strategy:
matrix:
include:
- os: ubuntu-24.04
goos: linux
goarch: amd64
- os: ubuntu-24.04-arm
goos: linux
goarch: arm64
- os: macos-latest
goos: darwin
goarch: arm64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Build binary
env:
CGO_ENABLED: 0
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
run: |
go build -ldflags="-s -w -X main.version=${{ github.ref_name }}" \
-o bin/app-${{ matrix.goos }}-${{ matrix.goarch }} .
- uses: actions/upload-artifact@v4
with:
name: binary-${{ matrix.goos }}-${{ matrix.goarch }}
path: bin/
docker:
needs: build-binaries
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
pattern: binary-linux-*
path: bin/
merge-multiple: true
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/metadata-action@v5
id: meta
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
release:
needs: [build-binaries, docker]
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
pattern: binary-*
path: dist/
merge-multiple: true
- name: Create checksums
run: |
cd dist
sha256sum * > checksums.txt
- uses: softprops/action-gh-release@v2
with:
files: |
dist/*
generate_release_notes: true
Terraform Patterns
Module Structure
modules/
└── service/
├── main.tf
├── variables.tf
├── outputs.tf
└── versions.tf
environments/
├── dev/
│ ├── main.tf
│ ├── backend.tf
│ └── terraform.tfvars
└── prod/
├── main.tf
├── backend.tf
└── terraform.tfvarsModule Pattern
modules/service/main.tf
resource "google_cloud_run_service" "main" {
name = var.name
location = var.region
template {
spec {
containers {
image = var.image
resources {
limits = {
cpu = var.cpu
memory = var.memory
}
}
}
}
}
traffic {
percent = 100
latest_revision = true
}
}modules/service/variables.tf
variable "name" {
description = "Service name"
type = string
}
variable "region" {
description = "GCP region"
type = string
default = "us-central1"
}
variable "image" {
description = "Container image"
type = string
}
variable "cpu" {
description = "CPU limit"
type = string
default = "1000m"
}
variable "memory" {
description = "Memory limit"
type = string
default = "512Mi"
}modules/service/outputs.tf
output "url" {
description = "Service URL"
value = google_cloud_run_service.main.status[0].url
}
output "name" {
description = "Service name"
value = google_cloud_run_service.main.name
}Environment Usage
environments/prod/main.tf
module "api" {
source = "../../modules/service"
name = "api"
region = "us-central1"
image = "gcr.io/myproject/api:${var.api_version}"
cpu = "2000m"
memory = "1Gi"
}environments/prod/backend.tf
terraform {
backend "gcs" {
bucket = "myproject-terraform-state"
prefix = "prod"
}
}Best Practices
Naming
# Let provider generate names
resource "google_storage_bucket" "main" {
name_prefix = "myapp-data-"
location = var.region
}
# Not hardcoded
resource "google_storage_bucket" "bad" {
name = "myapp-data-bucket" # Avoid
location = var.region
}Tagging
locals {
common_tags = {
Environment = var.environment
Project = var.project_name
ManagedBy = "terraform"
}
}
resource "aws_instance" "main" {
ami = var.ami
instance_type = var.instance_type
tags = merge(local.common_tags, { Name = "web-server" })
}Data Sources
data "google_project" "current" {}
data "google_compute_zones" "available" {
region = var.region
}Sensitive Outputs
output "database_password" {
value = random_password.db.result
sensitive = true
}Commands
terraform init # Initialize
terraform plan # Preview
terraform apply # Apply
terraform destroy # Destroy
terraform fmt # Format
terraform validate # Validate
terraform state list # List resources
terraform state show # Show resource