Docker na Proxmoxie — maszyna wirtualna z cloud-init

Docker w Proxmox — w kontenerze LXC czy w maszynie wirtualnej? Szablon Debiana 13 z cloud-init, instalacja Dockera i porządek w stosach Docker Compose.

Docker w Proxmox: LXC czy VM?

Dockera można uruchomić bezpośrednio na hoście Proxmoxa albo w kontenerze LXC. Oba podejścia działają — do pierwszej aktualizacji, po której coś przestaje działać. Dokumentacja Proxmoxa zaleca maszynę wirtualną i w tej serii trzymamy się tej zasady:

  • Host zostaje czysty. Docker modyfikuje reguły iptables i konfigurację sieci, co może kolidować z firewallem i mostami Proxmoxa.
  • Pełna izolacja. Kontener z luką w zabezpieczeniach ma do przejścia jądro maszyny wirtualnej, a nie wspólne jądro hosta.
  • Snapshot i backup całości. Jeden snapshot ZFS dysku VM obejmuje Dockera, obrazy, wolumeny i konfigurację.

Narzut maszyny wirtualnej z virtio to kilka procent CPU i kilkaset megabajtów RAM-u. W zamian dostajesz przewidywalność.

Szablon Debiana 13 z cloud-init

Zamiast instalować system z ISO, użyjemy oficjalnego obrazu chmurowego Debiana. Cloud-init przy pierwszym starcie ustawi użytkownika, klucz SSH i adres IP. Szablon przygotowujesz raz, a każda kolejna maszyna powstaje w kilka sekund.

Najpierw skopiuj swój publiczny klucz SSH na hosta Proxmox. Na swoim komputerze:

Okno terminala
scp ~/.ssh/id_ed25519.pub root@192.168.1.10:/root/admin.pub

Na hoście Proxmox:

Szablon VM 9000 — Debian 13 cloud
cd /var/lib/vz/template
wget -N https://cloud.debian.org/images/cloud/trixie/latest/debian-13-genericcloud-amd64.qcow2
qm create 9000 \
--name debian13-cloud \
--ostype l26 \
--machine q35 \
--bios ovmf \
--efidisk0 tank-vm:0,efitype=4m,pre-enrolled-keys=0 \
--cpu host \
--cores 2 \
--memory 2048 \
--net0 virtio,bridge=vmbr0 \
--scsihw virtio-scsi-single \
--scsi0 tank-vm:0,import-from=/var/lib/vz/template/debian-13-genericcloud-amd64.qcow2,discard=on,ssd=1,iothread=1 \
--ide2 tank-vm:cloudinit \
--boot order=scsi0 \
--serial0 socket \
--vga serial0 \
--agent enabled=1,fstrim_cloned_disks=1 \
--ciuser admin \
--sshkeys /root/admin.pub \
--ipconfig0 ip=dhcp
qm template 9000

Najważniejsze decyzje:

  • --cpu host przekazuje maszynie wszystkie instrukcje procesora (AES-NI, AVX). Wadą jest brak migracji na żywo między serwerami z różnymi CPU — w klastrze z dwoma identycznymi R620 to nie problem.
  • discard=on,ssd=1 — TRIM z maszyny trafia do ZFS, więc zwolnione miejsce wraca do puli.
  • virtio-scsi-single + iothread=1 — osobny wątek I/O dla każdego dysku.
  • --serial0 socket --vga serial0 — obrazy genericcloud wypisują konsolę na port szeregowy. W panelu Proxmoxa otwierasz ją przez Console → xterm.js.

Klonowanie maszyny docker01

VM 110 — docker01
qm clone 9000 110 --name docker01 --full
qm set 110 \
--cores 4 \
--memory 8192 \
--ipconfig0 ip=192.168.1.20/24,gw=192.168.1.1 \
--nameserver 192.168.1.1 \
--onboot 1
# Obraz chmurowy ma ~3 GB — powiększ dysk, cloud-init rozciągnie partycję przy starcie
qm disk resize 110 scsi0 +40G
qm start 110

Po kilkunastu sekundach zaloguj się ze swojego komputera:

Okno terminala
ssh admin@192.168.1.20

Przygotowanie systemu

W maszynie docker01 doinstaluj agenta QEMU, dzięki któremu Proxmox widzi adres IP maszyny, zamraża system plików przed snapshotem i poprawnie ją wyłącza:

Okno terminala
sudo apt update && sudo apt full-upgrade -y
sudo apt install -y qemu-guest-agent unattended-upgrades
sudo systemctl start qemu-guest-agent
sudo timedatectl set-timezone Europe/Warsaw

unattended-upgrades z domyślną konfiguracją Debiana instaluje automatycznie tylko poprawki bezpieczeństwa.

Instalacja Dockera z oficjalnego repozytorium

Pakiet docker.io z repozytorium Debiana jest starszy i nie zawiera wtyczki Compose v2. Instalujemy z repozytorium Dockera:

install-docker.sh
#!/usr/bin/env bash
set -euo pipefail
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
sudo tee /etc/apt/sources.list.d/docker.sources > /dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/debian
Suites: $(. /etc/os-release && echo "$VERSION_CODENAME")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Rotacja logów — bez tego logi kontenerów potrafią zapełnić dysk
sudo tee /etc/docker/daemon.json > /dev/null <<'EOF'
{
"log-driver": "local",
"log-opts": {
"max-size": "20m",
"max-file": "5"
}
}
EOF
sudo systemctl restart docker
sudo docker run --rm hello-world
Okno terminala
sudo usermod -aG docker admin
# Wyloguj się i zaloguj ponownie, żeby grupa zaczęła działać

Porządek w stosach

Każda usługa dostaje własny katalog z plikiem compose.yaml, sekretami w .env i danymi w podkatalogach. Kopia zapasowa /opt/stacks plus snapshot dysku VM to kompletna kopia całej warstwy aplikacji.

/opt/stacks/
├── cloudflared/
│ ├── compose.yaml
│ └── .env
└── vaultwarden/
├── compose.yaml
├── .env
└── data/
Okno terminala
sudo mkdir -p /opt/stacks
sudo chown admin:admin /opt/stacks

Pierwsza usługa to Vaultwarden — lekki, zgodny z Bitwardenem menedżer haseł. To dobry przykład aplikacji, którą chcesz mieć dostępną spoza domu. W następnej części wystawisz ją przez Cloudflare Tunnel:

/opt/stacks/vaultwarden/compose.yaml
services:
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
restart: unless-stopped
env_file: .env
environment:
DOMAIN: https://vault.${BASE_DOMAIN}
volumes:
- ./data:/data
networks:
- cf-tunnel
networks:
cf-tunnel:
external: true
/opt/stacks/vaultwarden/.env
BASE_DOMAIN=prywatnylab.pl
# Włącz na czas zakładania własnego konta, potem zmień na false i zrób docker compose up -d
SIGNUPS_ALLOWED=true

Sieć cf-tunnel łączy aplikacje z kontenerem cloudflared. Utwórz ją raz:

Okno terminala
docker network create cf-tunnel

Vaultwarden nie publikuje żadnego portu na zewnątrz maszyny. Będzie dostępny wyłącznie przez tunel, a jego interfejs webowy i tak wymaga HTTPS, bo kryptografia w przeglądarce działa tylko w bezpiecznym kontekście.

Okno terminala
cd /opt/stacks/vaultwarden
chmod 600 .env
docker compose up -d
docker compose logs -f

Aktualizacje kontenerów

Aktualizacja stosu
cd /opt/stacks/vaultwarden
docker compose pull
docker compose up -d
docker image prune -f

Zanim zaktualizujesz coś ważnego, zrób snapshot maszyny: qm snapshot 110 przed-aktualizacja na hoście Proxmox. Jeśli nowa wersja coś zepsuje, qm rollback 110 przed-aktualizacja przywróci całą maszynę w kilka sekund.

Kolejne maszyny w minutę

Szablon jest gotowy na kolejne usługi. Nowa maszyna z innym adresem to cztery polecenia:

Okno terminala
qm clone 9000 111 --name apps02 --full
qm set 111 --cores 2 --memory 4096 --ipconfig0 ip=192.168.1.21/24,gw=192.168.1.1 --onboot 1
qm disk resize 111 scsi0 +20G
qm start 111

Maszyna z Dockerem działa, Vaultwarden czeka w sieci cf-tunnel. W następnej części wystawisz go bezpiecznie do internetu — bez otwierania ani jednego portu na routerze.