DanpLab · Lab NoteArchitettura operativa

Docker, LXC and Kubernetes: containers in the homelab

Practical guide to using containers in the homelab: Docker Compose for application stacks, LXC on Proxmox for lightweight services, and Kubernetes (k3s) for advanced orchestration.

4 min readBased on real operational use
dockerkuberneteslinuxhomelabautomazione

Containers in the homelab: which to choose?

| Technology | Isolation | Overhead | Use case | |-----------|-----------|---------|-----------| | LXC (Proxmox) | Linux namespaces | Very low | System services (DNS, proxy) | | Docker | Namespaces + cgroups | Low | Stateless apps, microservices | | Kubernetes (k3s) | Pods + namespaces | Medium | Multi-node environments, HA | | VM KVM | Virtual hardware | High | Windows, different kernel |


LXC on Proxmox: lightweight services

LXC is ideal for services that require few resources:


pct create 101 local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst \
  --hostname nginx-proxy \
  --cores 1 \
  --memory 512 \
  --rootfs local-lvm:8 \
  --net0 name=eth0,bridge=vmbr0,ip=192.168.10.50/24,gw=192.168.10.1 \
  --unprivileged 1


pct start 101
pct exec 101 -- apt update && apt install -y nginx

LXC for common services


pct create 102 ... --hostname pihole
pct exec 102 -- curl -sSL https://install.pi-hole.net | bash


pct create 103 ... --hostname nginx-pm
pct exec 103 -- docker-compose up -d  # (con Docker nel LXC)


pct create 104 ... --hostname uptime

Docker Compose: application stacks

Complete homelab stack


version: '3.8'

services:
  # Nginx Proxy Manager - Reverse proxy con SSL automatico
  nginx-proxy:
    image: jc21/nginx-proxy-manager:latest
    restart: always
    ports:
      - "80:80"
      - "443:443"
      - "81:81"  # Admin UI
    volumes:
      - ./npm/data:/data
      - ./npm/letsencrypt:/etc/letsencrypt

  # Portainer - Gestione Docker via UI
  portainer:
    image: portainer/portainer-ce:latest
    restart: always
    ports:
      - "9443:9443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - portainer_data:/data

  # Watchtower - Aggiornamento automatico container
  watchtower:
    image: containrrr/watchtower
    restart: always
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    command: --schedule "0 4 * * *" --cleanup  # Update alle 4:00

  # Uptime Kuma - Monitoring servizi
  uptime-kuma:
    image: louislam/uptime-kuma:1
    restart: always
    ports:
      - "3001:3001"
    volumes:
      - uptime_data:/app/data

volumes:
  portainer_data:
  uptime_data:

Self-hosted AI stack


services:
  ollama:
    image: ollama/ollama:latest
    restart: always
    ports:
      - "11434:11434"
    volumes:
      - ollama_data:/root/.ollama
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]

  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    restart: always
    ports:
      - "3000:8080"
    environment:
      - OLLAMA_BASE_URL=http://ollama:11434
    depends_on:
      - ollama
    volumes:
      - webui_data:/app/backend/data

volumes:
  ollama_data:
  webui_data:

Dockerfile: custom image build


FROM python:3.11-slim

WORKDIR /app


COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt


COPY . .


RUN useradd -m appuser && chown -R appuser /app
USER appuser

EXPOSE 8000
CMD ["python", "app.py"]

docker build -t mio-registry:5000/myapp:1.0 .
docker push mio-registry:5000/myapp:1.0

k3s: lightweight Kubernetes for homelab

k3s is a CNCF-certified Kubernetes distribution, optimized for edge and homelab:


curl -sfL https://get.k3s.io | sh -


kubectl get nodes
kubectl get pods -A


cat /var/lib/rancher/k3s/server/node-token


curl -sfL https://get.k3s.io | K3S_URL=https://master-ip:6443 \
  K3S_TOKEN=<TOKEN> sh -

Deploy application on k3s


apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-homelab
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
        resources:
          limits:
            memory: "128Mi"
            cpu: "100m"
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx
  ports:
  - port: 80
    targetPort: 80
  type: LoadBalancer
kubectl apply -f deployment.yaml
kubectl get pods
kubectl get svc

Private Docker registry


services:
  registry:
    image: registry:2
    restart: always
    ports:
      - "5000:5000"
    environment:
      REGISTRY_AUTH: htpasswd
      REGISTRY_AUTH_HTPASSWD_REALM: "Registry Realm"
      REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
    volumes:
      - ./auth:/auth
      - registry_data:/var/lib/registry

  registry-ui:
    image: joxit/docker-registry-ui:latest
    ports:
      - "8080:80"
    environment:
      - REGISTRY_URL=http://registry:5000
    depends_on:
      - registry

Maintenance script

#!/bin/bash


echo "=== Docker Cleanup ==="


docker container prune -f


docker image prune -af --filter "until=720h"  # Più vecchie di 30 giorni


docker volume prune -f


docker network prune -f


docker system df

echo "=== Cleanup completato ==="

Container monitoring with Grafana


services:
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"

  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
    ports:
      - "8081:8080"

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    volumes:
      - grafana_data:/var/lib/grafana
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin

Best Practices

  • One process per container — one service per container
  • Non-root user in Dockerfiles — USER appuser
  • Health checksHEALTHCHECK CMD curl -f http://localhost/ || exit 1
  • Resource limits — always use resources.limits in production
  • Volume backups — automatic script for docker volume
  • Do not mount /var/run/docker.sock in production unless necessary
  • Avoid latest tags in production — use explicit versions

Resources