Skip to content

MkDocs Documentation Site on k3s

This documentation site itself runs on k3s! Here's how it's deployed and how to work with it.

Overview

  • Framework: MkDocs with Material theme
  • Hosting: Kubernetes Deployment + nginx
  • URL: doc.serlo.lu
  • Build: Docker multi-stage build bakes the static site

Architecture

graph LR
    A["docs/ folder<br/>(Markdown)"] -->|Build| B["Docker Image<br/>(builder)"]
    B -->|Multi-stage| C["nginx:alpine<br/>(runtime)"]
    C -->|Deploy| D["k8s Deployment"]
    D -->|Service| E["Traefik Ingress"]
    E -->|DNS| F["doc.serlo.lu"]

Building the Image Locally

Prerequisites

pip install mkdocs mkdocs-material

View Changes Locally

cd /path/to/homelab
mkdocs serve
# Open http://localhost:8000

Build Docker Image

# Build (reads mkdocs.yml + docs/ folder)
docker build -t ghcr.io/your-org/homelab-mkdocs:latest -f docs/docker/Dockerfile .

# Test locally
docker run -p 8000:80 ghcr.io/your-org/homelab-mkdocs:latest
# Open http://localhost:8000

Deploying to k3s

Method 1: Push to Registry + Deploy

# Push image
docker push ghcr.io/your-org/homelab-mkdocs:latest

# Deploy to cluster
kubectl apply -k apps/base/mkdocs

Method 2: ConfigMap (Build in CI)

Build in CI, create ConfigMap from static output:

# Build locally
mkdocs build -d site/

# Create ConfigMap manifest
kubectl -n mkdocs create configmap mkdocs-site --from-file=site --dry-run=client -o yaml > mkdocs-cm.yaml

# Apply
kubectl apply -f mkdocs-cm.yaml
kubectl apply -f docs/k8s/mkdocs/deployment.yaml
kubectl apply -f docs/k8s/mkdocs/service.yaml
kubectl apply -f docs/k8s/mkdocs/ingress.yaml

Kubernetes Manifests

apps/base/mkdocs/
├── kustomization.yaml
├── deployment.yaml
├── service.yaml
└── ingress.yaml

docs/k8s/mkdocs/
├── namespace.yaml
├── deployment.yaml
├── service.yaml
└── ingress.yaml

Uses the built Docker image; update image tag to deploy new versions.

  • Replicas: 2 (load-balanced)
  • Node selector: kubernetes.io/hostname: oracle (set to your node)
  • Resources: 50m CPU, 64Mi RAM (request); 200m CPU, 256Mi RAM (limit)
  • Host: doc.serlo.lu
  • TLS: Managed by cert-manager
  • Ingress class: Traefik

Updating Documentation

Workflow

  1. Edit markdown in docs/ folder (locally or via git)
  2. Commit & push to repository
  3. Build image (manually or in CI) and push to registry
  4. Deploy via kubectl apply -k apps/base/mkdocs or wait for Flux

Fast iteration (local)

# Terminal 1: Watch for changes
mkdocs serve

# Terminal 2: Edit docs/index.md, docs/apps/..., etc
# Changes appear instantly at http://localhost:8000

Production deployment (CI)

Scaffold a workflow in .github/workflows/deploy-docs.yml that: - Builds the Docker image - Pushes to registry - Updates apps/base/mkdocs/kustomization.yaml image tag - Commits changes (Flux detects & reconciles)

Similar pipeline using .gitlab-ci.yml

Customization

Change Theme Colors

Edit mkdocs.yml:

theme:
  palette:
    - scheme: default
      primary: indigo      # Change to: blue, teal, green, etc.
      accent: indigo       # Change accent color

Add Navigation Sections

Edit mkdocs.yml nav:

nav:
  - Home: index.md
  - New Section:
    - Page 1: path/to/page1.md
    - Page 2: path/to/page2.md

Use Material Theme Features

Examples:

!!! info "Title"
    Content here
=== "Tab 1"
    Content
=== "Tab 2"
    Other content
\`\`\`yaml
key: value   # (1)
\`\`\`
1. Explanation here

Monitoring & Logs

Check deployment status

# View pod status
kubectl -n mkdocs get pods
kubectl -n mkdocs describe deployment mkdocs-site

# Stream logs
kubectl -n mkdocs logs -f deployment/mkdocs-site

# Check ingress
kubectl -n mkdocs get ingress mkdocs

Access the site

# Via ingress (requires DNS + TLS setup)
https://doc.serlo.lu

# Via port-forward (local testing)
kubectl -n mkdocs port-forward svc/mkdocs 8000:80
# Then: http://localhost:8000

Troubleshooting

"image pull backoff"

# Verify image exists in registry
docker pull ghcr.io/your-org/homelab-mkdocs:latest

# Check pod events
kubectl -n mkdocs describe pod <pod-name>

"Ingress not working"

# Verify ingress is created
kubectl -n mkdocs get ingress -o wide

# Check Traefik is routing to service
kubectl -n mkdocs get svc mkdocs

# Verify DNS
nslookup doc.serlo.lu

"Page returns 404"

  1. Ensure mkdocs build was run locally (generates site/ folder)
  2. Verify image contains the correct site/ output
  3. Check nginx logs: kubectl -n mkdocs logs <pod>

Next Steps