Deployment Basics
10 examples to get you started with Deploy & K8s - 7 basic and 3 intermediate.
Search across all documentation pages
10 examples to get you started with Deploy & K8s - 7 basic and 3 intermediate.
kubectl and verify cluster access: kubectl cluster-info.mkdir deploylab && cd deploylab && go mod init example.com/deploylab.Produce an optimized Linux binary from your module root.
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath \
-ldflags="-s -w" -o bin/api ./cmd/apiCGO_ENABLED=0 yields a static binary suitable for scratch/distroless images.-trimpath removes local filesystem paths from binaries for reproducible builds.-ldflags="-s -w" strips debug symbols to shrink artifact size.Related: Deploying Go: Single Binary as a Superpower - why one file changes ops
Stamp git version into the binary for production debugging.
package main
import "fmt"
var (
version = "dev"
commit = "none"
)
func main() {
fmt.Printf("api %s (%s)\n", version, commit)
}go build -ldflags="-X main.version=1.2.3 -X main.commit=$(git rev-parse --short HEAD)" -o bin/api .-X sets string variables in main at link time.Related: CI/CD Pipelines for Go Services - automate version stamping in CI
Bind to PORT from the environment, the default pattern for PaaS and Kubernetes.
package main
import (
"fmt"
"log"
"net/http"
"os"
)
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello")
})
log.Fatal(http.ListenAndServe(":"+port, nil))
}:8080), not 127.0.0.1.PORT from env so platforms can assign ephemeral ports.Related: Multi-Stage Docker Builds for Go - package this server in a minimal image
Expose a cheap endpoint Kubernetes probes can call.
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
})Related: Probes, HPA & Pod Disruption Budgets - probe tuning and autoscaling
A simple two-line runtime image for local experiments.
FROM golang:1.26-bookworm AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /api ./cmd/api
FROM debian:bookworm-slim
COPY --from=build /api /api
EXPOSE 8080
ENTRYPOINT ["/api"]CGO_ENABLED=0 for portable Linux binaries.Related: Multi-Stage Docker Builds for Go - distroless and CA certificates
Tag with a semver or git SHA, never only latest in shared environments.
docker build -t deploylab/api:0.1.0 .
docker run --rm -p 8080:8080 -e PORT=8080 deploylab/api:0.1.0
curl -s localhost:8080/healthzPORT for quick smoke tests.Related: Deployment Best Practices - semver tags and rollback discipline
Run one replica with resource requests so the scheduler can place the pod.
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 1
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: deploylab/api:0.1.0
ports:
- containerPort: 8080
env:
- name: PORT
value: "8080"
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
memory: 128Mikubectl apply -f deployment.yaml
kubectl rollout status deployment/apiresources.requests help the scheduler and HPA baselines.image to a specific tag or digest, not a floating latest.Related: Kubernetes Deployments, Services & Ingress - Services, Ingress, and limits
Expose the Deployment inside the cluster and test without Ingress.
apiVersion: v1
kind: Service
metadata:
name: api
spec:
selector:
app: api
ports:
- port: 80
targetPort: 8080kubectl apply -f service.yaml
kubectl port-forward svc/api 8080:80
curl -s localhost:8080/healthzapi.default.svc.cluster.local) to pod IPs.targetPort must match the container listen port.Related: Kubernetes Deployments, Services & Ingress - Ingress rules and TLS
Wire liveness and readiness probes to /healthz.
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 3
periodSeconds: 10
readinessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 5initialDelaySeconds avoids killing pods still binding ports.periodSeconds and timeoutSeconds to your SLO and cold-start time.Related: Probes, HPA & Pod Disruption Budgets - PDBs and autoscaling
Kubernetes sends SIGTERM before SIGKILL; drain in-flight requests.
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
})
srv := &http.Server{Addr: ":8080", Handler: mux}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
}()
stop := make(chan os.Signal, 1)
signal.Notify(stop, syscall.SIGTERM, syscall.SIGINT)
<-stop
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal(err)
}
}Shutdown stops accepting new connections and waits for active requests.Related: Chaos Engineering Hooks for Go Microservices - validate behavior under failure
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 19, 2026