Docker rend un VPS immédiatement utile : une stack complète, application, base de données, cache, proxy, tient dans un fichier versionné et se redéploie à l'identique. L'installation prend trois minutes. Ce qui mérite votre attention vient après : la gestion des journaux, la persistance, et surtout la manière dont Docker interagit avec votre pare-feu.
1. Installer Docker Engine
Le paquet docker.io des dépôts Ubuntu retarde souvent de plusieurs
versions. Utilisez le dépôt officiel : c'est aussi le seul qui fournit le greffon
Compose v2 à jour.
sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
-o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
sudo docker run --rm hello-world
docker compose version
2. Utiliser Docker sans sudo
sudo usermod -aG docker $USER
newgrp docker
docker ps
À mesurer : appartenir au groupe docker équivaut
à un accès root sur la machine, un conteneur peut monter le système de
fichiers hôte. Sur une VM partagée par plusieurs personnes, préférez
sudo docker ou le mode rootless.
3. Limiter la croissance des journaux
Par défaut, Docker conserve la totalité de la sortie standard de chaque conteneur, sans rotation. Un service bavard peut remplir un disque de 40 Go en quelques semaines, c'est la panne la plus banale sur un VPS conteneurisé.
sudo tee /etc/docker/daemon.json <<'EOF'
{
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" },
"live-restore": true
}
EOF
sudo systemctl restart docker
La limite ne s'applique qu'aux conteneurs créés ensuite ; recréez les existants pour qu'elle prenne effet.
4. Le piège : Docker contourne UFW
C'est le point qui surprend le plus. Vous avez configuré
ufw default deny incoming, vous lancez :
docker run -d -p 5432:5432 postgres:16
…et votre base de données est accessible depuis Internet. Docker
insère ses propres règles NAT directement dans la chaîne PREROUTING
de netfilter, en amont des chaînes où UFW opère. Le trafic est traduit avant
d'être filtré. sudo ufw status continue d'afficher un pare-feu fermé,
et c'est exact, il n'est simplement pas sur le chemin.
La solution la plus simple : ne publiez jamais sur toutes les interfaces.
# au lieu de -p 5432:5432
docker run -d -p 127.0.0.1:5432:5432 postgres:16
Seuls le proxy et les services locaux atteignent alors le port. En Compose :
services:
db:
image: postgres:16
ports:
- "127.0.0.1:5432:5432"
Si un port doit rester public mais filtré par source, écrivez la règle dans la
chaîne DOCKER-USER, que Docker consulte avant ses propres règles et
ne réécrit jamais :
sudo iptables -I DOCKER-USER -i ens3 -p tcp --dport 5432 \
-s 203.0.113.0/24 -j ACCEPT
sudo iptables -I DOCKER-USER -i ens3 -p tcp --dport 5432 -j DROP
sudo apt install -y iptables-persistent # pour survivre au redémarrage
Vérifiez toujours depuis l'extérieur, jamais depuis la machine :
nmap -Pn -p 5432 198.51.100.42 depuis un autre hôte donne la réponse
qui compte.
5. Une première stack Compose
Un exemple complet : un proxy Caddy qui obtient et renouvelle seul ses certificats, devant une application. Seuls les ports 80 et 443 sont publiés.
# /srv/monapp/compose.yaml
services:
proxy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
depends_on:
- app
app:
image: ghcr.io/exemple/monapp:1.4.2
restart: unless-stopped
environment:
DATABASE_URL: postgres://app:${DB_PASSWORD}@db:5432/app
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 10s
retries: 5
volumes:
caddy_data:
pgdata:
cd /srv/monapp
echo "DB_PASSWORD=$(openssl rand -base64 24)" > .env
chmod 600 .env
docker compose up -d
docker compose ps
Trois détails qui font la différence sur la durée :
restart: unless-stopped relance les conteneurs après un redémarrage
de la VM ; les tags d'image sont figés plutôt que latest,
pour que docker compose pull reste une décision consciente ; les
données vivent dans des volumes nommés, pas dans la couche du conteneur.
6. Exploitation courante
docker compose logs -f --tail=100 app # suivre un service
docker compose pull && docker compose up -d # mettre à jour
docker compose exec db psql -U app # entrer dans un conteneur
docker system df # ce que Docker occupe sur le disque
docker system prune -a --volumes # ⚠ supprime aussi les volumes orphelins
Sauvegardes : un snapshot de VM capture l'état du disque, y
compris une base de données en cours d'écriture. Pour une restauration fiable,
doublez-le d'un pg_dump planifié vers un stockage distinct. Le
snapshot vous ramène la machine ; le dump vous ramène des données cohérentes.
Checklist
- Docker installé depuis le dépôt officiel, Compose v2 disponible.
- Rotation des journaux configurée dans
daemon.json. - Aucun port publié sur
0.0.0.0sans intention explicite. - Exposition vérifiée depuis une machine extérieure.
restart: unless-stoppedsur tout ce qui doit survivre à un redémarrage.- Volumes nommés pour les données, sauvegarde logique en complément du snapshot.