blue + white • beginner to advanced • single-file tutorial
Docker and Docker Compose from simple containers to secure Kubernetes delivery
Learn how to install Docker, build images, run containers, compose multi-service apps, switch between lite and full stacks, add OCR/doc conversion tools, run a small Go service, test failover/load balancing, secure nginx, use mTLS patterns, and move toward Kind, Argo CD, and Rancher.
What you will build
Table of contents
Overview: what Docker solves
Docker packages your application with its runtime, libraries, and startup command into an image. A container is a running copy of that image. Docker Compose lets you describe multiple services in one YAML file and start them together.
A blueprint: your app, dependencies, OS packages, and default command.
A running process from an image. It can be stopped, restarted, logged, and inspected.
A local application stack: API, database, proxy, queues, OCR tools, volumes, and networks.
Install Docker on macOS and Windows
macOS setup
- Install Docker Desktop for Mac.
- Open Docker Desktop and finish the first-run setup.
- Open Terminal and verify Docker and Compose.
docker --version
docker compose version
docker run hello-world
Windows setup
- Install Docker Desktop for Windows.
- Enable WSL 2 integration when prompted.
- Use PowerShell or Windows Terminal.
docker --version
docker compose version
docker run hello-world
wsl --status
Run your first container
Start with a tiny nginx web server so you can see how ports work.
# Run nginx in the background and map host port 8080 to container port 80
docker run --name web-demo -d -p 8080:80 nginx:alpine
# Visit http://localhost:8080
# See running containers
docker ps
# View logs
docker logs web-demo
# Stop and remove
docker stop web-demo
docker rm web-demo
Dockerfile: simple Go API and multi-stage build
A Dockerfile is a recipe. This example compiles a Go app in one stage and runs only the small binary in another stage.
main.go
package main
import (
"fmt"
"log"
"net/http"
"os"
)
func main() {
port := getenv("PORT", "8080")
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello from Go container! host=%s\n", getenv("HOSTNAME", "local"))
})
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "ok")
})
log.Fatal(http.ListenAndServe(":"+port, nil))
}
func getenv(k, fallback string) string {
if v := os.Getenv(k); v != "" { return v }
return fallback
}
Dockerfile
# syntax=docker/dockerfile:1
FROM golang:1.23-alpine AS build
WORKDIR /src
COPY go.mod ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /out/app .
FROM gcr.io/distroless/static-debian12:nonroot
WORKDIR /app
COPY --from=build /out/app /app/app
EXPOSE 8080
USER nonroot:nonroot
ENTRYPOINT ["/app/app"]
go mod init example.com/docker-go-api
docker build -t local/go-api:lite .
docker run --rm -p 8080:8080 local/go-api:lite
Docker Compose: lite and full profiles
Compose profiles let you keep optional services in the same file. The lite stack runs only the API and nginx. The full stack adds Postgres, Redis, and document processing tools.
compose.yml
name: doc-platform
services:
api:
build:
context: .
dockerfile: Dockerfile
image: local/go-api:lite
environment:
PORT: 8080
DATABASE_URL: postgres://app:app@db:5432/appdb?sslmode=disable
REDIS_URL: redis://redis:6379/0
expose:
- "8080"
healthcheck:
test: ["CMD", "/app/app", "--healthcheck"]
interval: 10s
timeout: 3s
retries: 3
restart: unless-stopped
nginx:
image: nginx:1.27-alpine
ports:
- "8080:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- api
restart: unless-stopped
db:
image: postgres:17-alpine
profiles: ["full"]
environment:
POSTGRES_DB: appdb
POSTGRES_USER: app
POSTGRES_PASSWORD: app
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
profiles: ["full"]
doc-tools:
build:
context: .
dockerfile: Dockerfile.full
profiles: ["full", "ocr"]
volumes:
- ./samples:/work/samples
command: ["sleep", "infinity"]
volumes:
pgdata:
Run profiles
# Lite stack: api + nginx
docker compose up --build
# Full stack: api + nginx + db + redis + doc-tools
docker compose --profile full up --build
# OCR tools only with core services
docker compose --profile ocr up --build
# Stop and remove containers
docker compose down
# Stop and remove volumes too
docker compose down -v
Full image with Tesseract OCR and document conversion tools
Use a separate full image when you need OCR, PDF, and document conversion packages. Keep your API image small and use a worker/tool image for heavy packages.
# Dockerfile.full
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive \
TESSDATA_PREFIX=/usr/share/tesseract-ocr/5/tessdata
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl bash file jq unzip git \
tesseract-ocr tesseract-ocr-eng \
poppler-utils imagemagick ghostscript \
libreoffice pandoc \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /work
CMD ["bash"]
# Test OCR and document utilities
docker compose --profile ocr run --rm doc-tools tesseract --version
docker compose --profile ocr run --rm doc-tools pdftotext -v
docker compose --profile ocr run --rm doc-tools libreoffice --version
# Example OCR command
docker compose --profile ocr run --rm doc-tools \
tesseract /work/samples/page.png stdout -l eng --psm 6
Docker and Compose CLI cheat sheet
| Goal | Command |
|---|---|
| Build image | docker build -t app:dev . |
| Run container | docker run --rm -p 8080:8080 app:dev |
| Shell into container | docker exec -it NAME sh |
| Compose start | docker compose up --build |
| Scale API | docker compose up --scale api=3 |
| Merged config | docker compose config |
| Clean unused | docker system prune |
nginx load balancing and failover
Compose can run multiple replicas of your API. nginx can route requests to the service name and Docker DNS will resolve containers on the Compose network.
# nginx.conf
events {}
http {
upstream api_pool {
server api:8080 max_fails=3 fail_timeout=10s;
}
server {
listen 80;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header Referrer-Policy no-referrer always;
location / {
proxy_pass http://api_pool;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /health {
proxy_pass http://api_pool/health;
}
}
}
# Run three API replicas behind nginx
docker compose up --build --scale api=3
# Watch traffic rotate across hostnames
for i in {1..10}; do curl http://localhost:8080; done
Security, secrets, image hygiene, and mTLS pattern
Security checklist
- Use small runtime images and multi-stage builds.
- Run as a non-root user.
- Pin image versions for reproducible builds.
- Use Docker secrets or environment injection carefully.
- Scan images in CI before deployment.
- Do not bake passwords, API keys, or certificates into images.
- Use TLS termination and forward only trusted headers.
mTLS nginx idea
server {
listen 443 ssl;
server_name local.example.test;
ssl_certificate /etc/nginx/certs/server.crt;
ssl_certificate_key /etc/nginx/certs/server.key;
# Client certificate verification
ssl_client_certificate /etc/nginx/certs/ca.crt;
ssl_verify_client on;
location / {
proxy_pass http://api_pool;
proxy_set_header X-Client-Verify $ssl_client_verify;
proxy_set_header X-Client-DN $ssl_client_s_dn;
}
}
Move from Compose to Kubernetes with Kind
Kind runs a local Kubernetes cluster using container nodes. It is good for local learning, CI testing, and trying manifests before cloud deployment.
Kind cluster
# Install kind, kubectl, and Docker first
kind create cluster --name docker-lab
kubectl cluster-info --context kind-docker-lab
# Build image locally and load it into Kind
docker build -t go-api:kind .
kind load docker-image go-api:kind --name docker-lab
Kubernetes Deployment + Service
apiVersion: apps/v1
kind: Deployment
metadata:
name: go-api
spec:
replicas: 3
selector:
matchLabels:
app: go-api
template:
metadata:
labels:
app: go-api
spec:
containers:
- name: api
image: go-api:kind
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /health
port: 8080
---
apiVersion: v1
kind: Service
metadata:
name: go-api
spec:
selector:
app: go-api
ports:
- port: 80
targetPort: 8080
type: ClusterIP
kubectl apply -f k8s.yml
kubectl get pods
kubectl port-forward service/go-api 8080:80
curl http://localhost:8080
Argo CD GitOps setup
Argo CD watches a Git repository and compares desired Kubernetes manifests against what is running in the cluster. If the cluster drifts, Argo CD shows the difference and can sync it back.
# Install Argo CD into Kind or another Kubernetes cluster
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
kubectl -n argocd get pods
# Port-forward UI
kubectl port-forward svc/argocd-server -n argocd 8081:443
# Example Application manifest
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: go-api
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/YOUR-ORG/YOUR-REPO.git
targetRevision: main
path: k8s
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
Rancher and Rancher Desktop tools
Rancher Desktop for local work
Rancher Desktop can run Kubernetes locally and includes container runtime options. It is useful when you want a Docker Desktop alternative or a local Kubernetes environment.
# After installing Rancher Desktop with Kubernetes enabled
kubectl get nodes
kubectl create deployment demo --image=nginx
kubectl expose deployment demo --port=80 --type=ClusterIP
Rancher Manager idea
Rancher Manager is used to manage multiple Kubernetes clusters, users, RBAC, apps, and policies from one interface.
# Lab-only single-node example idea
# For production, follow official HA install guidance.
docker run -d --restart=unless-stopped \
-p 80:80 -p 443:443 \
--privileged \
rancher/rancher:latest
Practice labs: simple to complex
Lab 1: nginx container
Run nginx, map a port, inspect logs, and clean up.
Lab 2: Go API image
Build a multi-stage Dockerfile and run your own API.
Lab 3: Compose lite/full
Use profiles to switch between small and full development stacks.
Lab 4: OCR utilities
Run Tesseract and document conversion commands in a tool container.
Lab 5: Failover
Scale API replicas and test nginx proxy behavior.
Lab 6: Kind + Argo CD
Load a local image into Kind and let Argo CD manage manifests.
Troubleshooting guide
Port already allocated
Another process is using the port. Change the left side of the port mapping, for example 8081:80, or stop the process using the port.
Container exits immediately
Check docker logs NAME. The app may have crashed, the command may have finished, or a required environment variable may be missing.
Compose service cannot connect to database
Inside Compose, use service names like db, not localhost. localhost inside a container means that same container.
Tesseract cannot find eng.traineddata
Install tesseract-ocr-eng and set TESSDATA_PREFIX to the tessdata directory used by the image.
Kind cannot pull local image
Run kind load docker-image IMAGE:TAG --name CLUSTER and use imagePullPolicy: IfNotPresent for local images.