Tutorials
Short, reproducible procedures: build an image, write a pipeline, run your first Terraform cycle, and diagnose a crashing pod.
Step-by-step procedures
Each tutorial can be completed on a laptop. Commands are copyable. They follow the official tool documentation: check the docs for your version.
Build a small multi-stage Docker image
Docker
Compile a tiny Go web server in one stage and ship only the binary in a minimal, non-root runtime image.
Prerequisites
- Docker installed and running
- No Go installation needed: the build happens in the container
Steps
1. Create the application.
module hello
go 1.22package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello from a multi-stage image")
})
log.Fatal(http.ListenAndServe(":8080", nil))
}2. Write the Dockerfile. The first stage has the compiler. The second stage contains only the binary.
FROM golang:1.22 AS build
WORKDIR /src
COPY go.mod main.go ./
RUN CGO_ENABLED=0 go build -o /out/app .
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /out/app /app
USER nonroot
EXPOSE 8080
ENTRYPOINT ["/app"]3. Build and run.
docker build -t hello:1 .
docker run --rm -p 8080:8080 hello:1Verify
In a second terminal:
curl http://localhost:8080/
docker images hello:1Common pitfalls
- The final image has no shell, so
docker exec ... shwill fail. That is expected. Use an image with a shell temporarily if you need to debug. CGO_ENABLED=0is needed so the binary is statically linked and runs on the static base image.- Pin image versions in real projects and review base image updates deliberately.
Write a first GitLab CI pipeline with artifacts
GitLab CI
Create two stages, pass a file from the first job to the second, and see the pipeline fail when a check fails.
Prerequisites
- A GitLab project with shared or project runners enabled
Steps
1. Add this file at the root of the repository.
stages:
- build
- test
build-job:
stage: build
image: alpine:3.20
script:
- mkdir -p dist
- echo "artifact" > dist/app.txt
artifacts:
paths:
- dist/
test-job:
stage: test
image: alpine:3.20
script:
- test -f dist/app.txt2. Commit and push. Open the project’s CI/CD, then Pipelines, page to watch it run.
3. Break it on purpose. Change the test to test -f dist/missing.txt, push, and observe the failed job and its log.
Verify
The first run should show both jobs green. After the change, test-job fails and, if merge requests require a passing pipeline, the merge is blocked.
Common pitfalls
- Jobs in later stages receive artifacts from earlier stages by default. Use
dependenciesorneedsto control this. - Artifacts are for passing outputs between jobs. Use
cachefor dependency downloads.
Your first Terraform workflow without any cloud account
Terraform
Learn init, plan, apply and destroy using the local provider, so nothing is billed.
Prerequisites
- Terraform CLI installed
Steps
1. Create the configuration.
terraform {
required_providers {
local = {
source = "hashicorp/local"
}
}
}
variable "greeting" {
type = string
default = "Hello from Terraform"
}
resource "local_file" "hello" {
filename = "${path.module}/hello.txt"
content = "${var.greeting}\n"
}
output "file_path" {
value = local_file.hello.filename
}2. Run the cycle.
terraform init
terraform fmt
terraform validate
terraform plan
terraform apply3. Change the variable and read the plan.
terraform plan -var="greeting=Hello again"4. Clean up.
terraform destroyVerify
After apply, cat hello.txt shows the greeting. The second plan shows the file content changing, and destroy removes the file.
Common pitfalls
- Always read the plan. Look for lines marked as replaced or destroyed, not just added or changed.
- The state file (
terraform.tfstate) can contain sensitive values. Do not commit it. Use a remote backend for shared work.
Diagnose a Kubernetes pod in CrashLoopBackOff
Kubernetes
A repeatable order of checks that keeps you from guessing. Read-only commands first, then act.
Prerequisites
- kubectl configured for the target cluster and namespace
- Permission to read pods, events and logs
Steps
1. Find the failing pod.
kubectl get pods -n <namespace>2. Read the pod description. Look at State, Last State, Reason, Exit Code, the container’s restart count and the Events at the bottom.
kubectl describe pod <pod> -n <namespace>3. Read the logs of the previous, crashed container.
kubectl logs <pod> -n <namespace> --previous4. Check recent events in the namespace.
kubectl get events -n <namespace> --sort-by=.lastTimestamp5. Interpret what you see.
OOMKilledas the last reason: the container exceeded its memory limit. Compare usage with the limit before raising it.- A non-zero exit code with an application stack trace in the logs: it is an application or configuration error, not a Kubernetes one.
- Failed liveness probe events: the probe may be too strict or start too early. Review
initialDelaySecondsor use a startup probe. - Error pulling image events would show
ImagePullBackOffinstead, which is a different problem (registry, credentials or tag).
6. If the cause is a bad release, roll back.
kubectl rollout undo deployment/<name> -n <namespace>
kubectl rollout status deployment/<name> -n <namespace>Verify
The pod should reach Running and stay ready. Watch it with kubectl get pods -w for a few minutes, not seconds, because the back-off delay grows between restarts.
Common pitfalls
- Do not delete pods repeatedly hoping it fixes itself. Restarts hide the evidence.
- Raising limits without measuring can move the problem to other workloads on the node.
- Restart-happy behaviour can cascade to dependent services. Change one thing at a time and record it.
benmabrouk.fr: free DevOps and SRE learning resources, written from production experience.