Deployment Basics
10 examples to get you started with Deploy & K8s - 7 basic and 3 intermediate.
Prerequisites
- Install Go 1.26.x or later from go.dev/dl.
- Install Docker for image builds and a local Kubernetes cluster (kind, minikube, or Docker Desktop Kubernetes).
- Install
kubectland verify cluster access:kubectl cluster-info. - Create a module:
mkdir deploylab && cd deploylab && go mod init example.com/deploylab.
Basic Examples
1. Release Binary with go build
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=0yields a static binary suitable for scratch/distroless images.-trimpathremoves 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
2. Embed Version Metadata at Build Time
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 .-Xsets string variables inmainat link time.- Log version at startup so incidents map to CI build numbers.
- Keep unstripped debug builds in artifact storage for symbolication.
Related: CI/CD Pipelines for Go Services - automate version stamping in CI
3. Minimal HTTP Server for Containers
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))
}- Containers should listen on all interfaces (
:8080), not127.0.0.1. - Read
PORTfrom env so platforms can assign ephemeral ports. - Add explicit timeouts before production; defaults are unsafe behind load balancers.
Related: Multi-Stage Docker Builds for Go - package this server in a minimal image
4. Liveness-Ready Health Endpoint
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"))
})- Liveness should answer "is the process up?" without hitting databases.
- Readiness may check dependencies; keep it fast to avoid flapping during rollouts.
- Return non-200 when the process should stop receiving traffic.
Related: Probes, HPA & Pod Disruption Budgets - probe tuning and autoscaling
5. Single-Stage Dockerfile (Learning Path)
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"]- Multi-stage keeps compile tools out of the runtime layer.
- Even learning Dockerfiles should set
CGO_ENABLED=0for portable Linux binaries. - Production teams move to distroless; this debian-slim stage is easier to debug locally.
Related: Multi-Stage Docker Builds for Go - distroless and CA certificates
6. Build and Run the Image Locally
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/healthz- Immutable tags tie running containers to CI artifacts.
- Map host port 8080 to container
PORTfor quick smoke tests. - Push the same tag your Kubernetes manifest references.
Related: Deployment Best Practices - semver tags and rollback discipline
7. Minimal Kubernetes Deployment
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.requestshelp the scheduler and HPA baselines.- Pin
imageto a specific tag or digest, not a floatinglatest. - Watch rollout status before declaring the deploy complete.
Related: Kubernetes Deployments, Services & Ingress - Services, Ingress, and limits
Intermediate Examples
8. ClusterIP Service and Port Forward
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/healthz- Services route stable DNS (
api.default.svc.cluster.local) to pod IPs. targetPortmust match the container listen port.- Port-forward is for dev; production uses Ingress or LoadBalancer Services.
Related: Kubernetes Deployments, Services & Ingress - Ingress rules and TLS
9. HTTP Probes on the Deployment
Wire liveness and readiness probes to /healthz.
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 3
periodSeconds: 10
readinessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 5initialDelaySecondsavoids killing pods still binding ports.- Readiness failure removes the pod from Service endpoints during startup.
- Tune
periodSecondsandtimeoutSecondsto your SLO and cold-start time.
Related: Probes, HPA & Pod Disruption Budgets - PDBs and autoscaling
10. Graceful Shutdown on SIGTERM
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)
}
}Shutdownstops accepting new connections and waits for active requests.- Match termination grace period in the pod spec to your shutdown timeout.
- During rollouts, readiness failure plus graceful drain prevents dropped 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).