D

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

Lite stack: Go API + nginx + health checks.
Full stack: Go API + Postgres + Redis + Tesseract OCR + LibreOffice/Pandoc style conversion utilities.
Kubernetes stack: Kind cluster, Deployment, Service, Ingress pattern, Argo CD GitOps, Rancher/Rancher Desktop overview.

Table of contents

01

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.

Image

A blueprint: your app, dependencies, OS packages, and default command.

Container

A running process from an image. It can be stopped, restarted, logged, and inspected.

Compose

A local application stack: API, database, proxy, queues, OCR tools, volumes, and networks.

02

Install Docker on macOS and Windows

macOS setup

  1. Install Docker Desktop for Mac.
  2. Open Docker Desktop and finish the first-run setup.
  3. Open Terminal and verify Docker and Compose.
docker --version
docker compose version
docker run hello-world

Windows setup

  1. Install Docker Desktop for Windows.
  2. Enable WSL 2 integration when prompted.
  3. Use PowerShell or Windows Terminal.
docker --version
docker compose version
docker run hello-world
wsl --status
Beginner note: Docker Desktop includes Docker Engine, Docker CLI, and Docker Compose. You do not need to install Compose separately when using Docker Desktop.
03

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
04

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
05

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
Pattern: use profiles for optional local tools, debugging containers, OCR utilities, admin UIs, and heavy services that you do not always need.
06

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
07

Docker and Compose CLI cheat sheet

GoalCommand
Build imagedocker build -t app:dev .
Run containerdocker run --rm -p 8080:8080 app:dev
Shell into containerdocker exec -it NAME sh
Compose startdocker compose up --build
Scale APIdocker compose up --scale api=3
Merged configdocker compose config
Clean unuseddocker system prune
08

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
09

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;
  }
}
10

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
11

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
12

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
13

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.

14

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.

15

Official references