--- sidebar_position: 3 tags: [docker, docker-compose, conteneurisation, homelab] last_update: date: 2025-11-25 --- # Docker et Docker Compose ## Qu'est-ce que Docker ? Docker est une **plateforme de conteneurisation** qui permet d'empaqueter des applications et leurs dépendances dans des conteneurs légers et isolés. ### Les conteneurs : révolution de l'infrastructure moderne Un conteneur est une unité logicielle standardisée qui contient : - L'application elle-même - Toutes ses dépendances (bibliothèques, runtime, outils système) - Un système de fichiers isolé - Des variables d'environnement et configuration **Différence avec les machines virtuelles** : - **Conteneur** : Partage le noyau de l'OS hôte, démarre en quelques secondes, très léger (~Mo) - **VM** : Émule un OS complet, démarre en minutes, plus lourd (~Go) ### Avantages de Docker 1. **Portabilité** : "Runs anywhere" - fonctionne identiquement en développement, test et production 2. **Isolation** : Chaque conteneur est isolé, évitant les conflits de dépendances 3. **Légèreté** : Consomme moins de ressources qu'une VM (pas de virtualisation complète) 4. **Rapidité** : Démarrage instantané des applications 5. **Reproductibilité** : Image Docker = environnement identique à chaque fois 6. **Écosystème** : Docker Hub contient des milliers d'images prêtes à l'emploi ## Docker Compose : orchestration simplifiée Docker Compose est un **outil d'orchestration** pour définir et gérer des applications multi-conteneurs. ### Pourquoi Docker Compose ? Sans Compose, déployer une application avec plusieurs conteneurs (app + base de données + cache + ...) nécessite de longues commandes `docker run` difficiles à maintenir. Avec Compose : - **Configuration déclarative** : Tout est défini dans un fichier `compose.yml` - **Gestion groupée** : Démarrer/arrêter tous les services en une commande - **Réseaux automatiques** : Les conteneurs communiquent facilement entre eux - **Volumes persistants** : Gestion simple du stockage - **Variables d'environnement** : Configuration flexible via fichiers `.env` ### Fichier compose.yml Un fichier Compose définit : - Les **services** (conteneurs) - Les **réseaux** (communication entre conteneurs) - Les **volumes** (persistance des données) - Les **variables d'environnement** (configuration) ## Exemples de stacks Docker Compose Mes stacks Docker Compose sont disponibles dans le dépôt Ansible sous `stacks/`. Voici quelques exemples représentatifs : ### Exemple 1 : Traefik - Reverse Proxy avancé Traefik est le point d'entrée de toute l'infrastructure. Ce compose illustre une configuration avancée avec **deux instances Traefik** (publique et privée) : ```yaml services: traefik-public: image: traefik:v3 container_name: traefik-public restart: unless-stopped ports: - "192.168.1.2:80:80" - "192.168.1.2:443:443" extra_hosts: - "host.docker.internal:host-gateway" networks: - traefik_network volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./traefik-public.yml:/etc/traefik/traefik.yml:ro - ./dynamic-public:/etc/traefik/dynamic:ro - ./letsencrypt-public:/letsencrypt - /var/log/traefik:/var/log/traefik labels: - "traefik.enable=true" - "traefik.http.routers.traefik-dashboard-public.rule=Host(`traefik-public.local.tellserv.fr`)" - "traefik.http.routers.traefik-dashboard-public.entrypoints=local" - "traefik.http.routers.traefik-dashboard-public.tls.certresolver=cloudflare-local" - "traefik.http.routers.traefik-dashboard-public.tls=true" - "traefik.http.routers.traefik-dashboard-public.service=api@internal" - "traefik.http.middlewares.crowdsec-bouncer.forwardauth.address=http://crowdsec-bouncer:8080/api/v1/forwardAuth" environment: - CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN} - TZ=Europe/Paris traefik-private: image: traefik:v3 container_name: traefik-private restart: unless-stopped ports: - "192.168.1.3:80:80" - "192.168.1.3:443:443" networks: - traefik_network volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./traefik-private.yml:/etc/traefik/traefik.yml:ro - ./dynamic-private:/etc/traefik/dynamic:ro - ./letsencrypt-private:/letsencrypt labels: - "traefik.enable=true" - "traefik.http.routers.traefik-dashboard-local.rule=Host(`traefik-private.local.tellserv.fr`)" environment: - TZ=Europe/Paris - CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN} networks: traefik_network: external: true ``` **Points clés** : - **Deux instances** : Séparation publique (Internet) et privée (réseau local uniquement) - **Socket Docker** : Traefik détecte automatiquement les nouveaux conteneurs via `/var/run/docker.sock` - **Certificats Let's Encrypt** : Génération automatique avec DNS-01 challenge (Cloudflare) - **Labels Traefik** : Configuration dynamique via labels Docker - **Middleware CrowdSec** : Intégration avec CrowdSec pour bloquer les IPs malveillantes - **Réseau externe** : Tous les services se connectent au réseau `traefik_network` ### Exemple 2 : Photoprism - Application avec base de données Photoprism illustre une stack applicative classique (app + DB) avec configuration avancée : ```yaml services: photoprism: image: photoprism/photoprism:241021 stop_grace_period: 10s depends_on: - mariadb restart: unless-stopped security_opt: - seccomp:unconfined - apparmor:unconfined working_dir: "/photoprism" volumes: - "/mnt/storage/photos:/photoprism/import" - "/mnt/storage/photoprism/originals:/photoprism/originals" - "/mnt/storage/photoprism/storage:/photoprism/storage" environment: - PHOTOPRISM_DATABASE_DRIVER=mysql - PHOTOPRISM_DATABASE_SERVER=mariadb:3306 - PHOTOPRISM_DATABASE_NAME=photoprism - PHOTOPRISM_DATABASE_USER=${MARIADB_USER} - PHOTOPRISM_DATABASE_PASSWORD=${PHOTOPRISM_DATABASE_PASSWORD} - PHOTOPRISM_ADMIN_USER=${PHOTOPRISM_ADMIN_USER} - PHOTOPRISM_ADMIN_PASSWORD=${PHOTOPRISM_ADMIN_PASSWORD} - PHOTOPRISM_SITE_URL=https://photoprism.tellserv.fr/ - PHOTOPRISM_HTTP_COMPRESSION=gzip - PHOTOPRISM_JPEG_QUALITY=85 networks: - traefik_network labels: - "traefik.enable=true" - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-local.rule=Host(`${COMPOSE_PROJECT_NAME}.local.tellserv.fr`)" - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-local.entryPoints=local" - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-local.tls=true" - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-prod.rule=Host(`${COMPOSE_PROJECT_NAME}.tellserv.fr`)" - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-prod.entryPoints=websecure" - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-prod.tls.certResolver=cloudflare" - "traefik.http.services.${COMPOSE_PROJECT_NAME}.loadbalancer.server.port=2342" - "com.centurylinklabs.watchtower.enable=true" mariadb: image: mariadb:11 restart: unless-stopped stop_grace_period: 5s command: > --innodb-buffer-pool-size=512M --transaction-isolation=READ-COMMITTED --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=512 volumes: - ./database:/var/lib/mysql environment: - MARIADB_DATABASE=photoprism - MARIADB_USER=${MARIADB_USER} - MARIADB_PASSWORD=${MARIADB_PASSWORD} - MARIADB_ROOT_PASSWORD=${MARIADB_ROOT_PASSWORD} networks: - traefik_network networks: traefik_network: external: true ``` **Points clés** : - **Dépendances** : `depends_on` garantit que MariaDB démarre avant Photoprism - **Volumes montés** : Accès au stockage MergerFS (`/mnt/storage`) pour les photos - **Base de données** : MariaDB optimisée pour Photoprism (buffer pool, character set UTF-8) - **Variables d'environnement** : Secrets injectés via fichier `.env` (non versionné) - **Double exposition** : Accessible en local (`.local.tellserv.fr`) et sur Internet (`.tellserv.fr`) - **Watchtower** : Label pour activer les mises à jour automatiques - **Optimisations DB** : Configuration MariaDB adaptée (buffer pool, connexions, charset) ### Exemple 3 : Mobilizon - Application multi-conteneurs avec réseau interne Mobilizon démontre l'utilisation de **réseaux Docker multiples** (externe + interne) : ```yaml services: mobilizon: user: "1000:1000" restart: always image: docker.io/framasoft/mobilizon env_file: .env depends_on: - db volumes: - ./uploads:/var/lib/mobilizon/uploads - ./tzdata:/var/lib/mobilizon/tzdata networks: - traefik_network - mobilizon_internal labels: - "traefik.enable=true" - "traefik.http.routers.mobilizon-local.rule=Host(`mobilizon.local.tellserv.fr`)" - "traefik.http.routers.mobilizon-local.entryPoints=local" - "traefik.http.routers.mobilizon-prod.rule=Host(`mobilizon.tellserv.fr`)" - "traefik.http.routers.mobilizon-prod.entryPoints=websecure" - "traefik.http.routers.mobilizon-prod.tls.certResolver=cloudflare" - "traefik.http.services.mobilizon.loadbalancer.server.port=5005" db: image: docker.io/postgis/postgis:15-3.4 restart: always env_file: .env volumes: - ./db:/var/lib/postgresql/data:z networks: - mobilizon_internal networks: mobilizon_internal: ipam: driver: default traefik_network: external: true ``` **Points clés** : - **Deux réseaux** : - `traefik_network` (externe) : Mobilizon communique avec Traefik - `mobilizon_internal` (interne) : Communication privée entre Mobilizon et PostgreSQL - **Sécurité** : La base de données n'est pas exposée sur le réseau Traefik - **PostgreSQL avec PostGIS** : Extension géographique pour gérer les événements géolocalisés - **User ID** : Exécution avec UID/GID spécifique pour gérer les permissions fichiers - **Volume avec SELinux** : Flag `:z` pour la compatibilité SELinux ### Exemple 4 : Vaultwarden - Gestion des secrets Vaultwarden (gestionnaire de mots de passe) montre une configuration axée sécurité : ```yaml services: vaultwarden: image: vaultwarden/server:1.32.7 container_name: vaultwarden restart: unless-stopped environment: - TZ=Europe/Paris - ADMIN_TOKEN=${VAULTWARDEN_ADMIN_TOKEN} - SIGNUPS_ALLOWED=${SIGNUPS_ALLOWED} - SMTP_FROM=${SMTP_FROM} - SMTP_HOST=${SMTP_HOST} - SMTP_PORT=${SMTP_PORT} - SMTP_SECURITY=${SMTP_SECURITY} - SMTP_USERNAME=${SMTP_USERNAME} - SMTP_PASSWORD=${SMTP_PASSWORD} - EXPERIMENTAL_CLIENT_FEATURE_FLAGS=ssh-key-vault-item,ssh-agent volumes: - ./vw-data:/data networks: - traefik_network labels: - "traefik.enable=true" - "traefik.http.routers.vaultwarden-local.rule=Host(`vaultwarden.local.tellserv.fr`)" - "traefik.http.routers.vaultwarden-prod.rule=Host(`vaultwarden.tellserv.fr`)" - "traefik.http.routers.vaultwarden-prod.tls.certResolver=cloudflare" - "com.centurylinklabs.watchtower.enable=true" networks: traefik_network: external: true ``` **Points clés** : - **Secrets via .env** : Tous les mots de passe et tokens dans des variables d'environnement - **Configuration SMTP** : Envoi d'emails pour les notifications et récupération de compte - **Features expérimentales** : Support des clés SSH dans le coffre-fort - **Volume de données** : Persistance du coffre-fort dans `./vw-data` - **Exposition sécurisée** : HTTPS obligatoire via Traefik avec Let's Encrypt ## Patterns et bonnes pratiques ### 1. Réseau externe `traefik_network` Tous mes services utilisent un **réseau Docker externe** partagé : ```yaml networks: traefik_network: external: true ``` Avantages : - Traefik détecte automatiquement les nouveaux services - Communication entre services via leurs noms (ex: `http://vaultwarden`) - Isolation par défaut (services non connectés ne peuvent pas communiquer) ### 2. Labels Traefik pour la configuration dynamique Au lieu de fichiers de configuration statiques, j'utilise des **labels Docker** : ```yaml labels: - "traefik.enable=true" - "traefik.http.routers.myapp.rule=Host(`myapp.tellserv.fr`)" - "traefik.http.routers.myapp.tls.certResolver=cloudflare" ``` Avantages : - Configuration colocalisée avec le service - Déploiement d'un nouveau service = ajout automatique dans Traefik - Pas de rechargement manuel de Traefik ### 3. Variables d'environnement avec fichiers .env Tous les secrets sont extraits dans des fichiers `.env` : ```env VAULTWARDEN_ADMIN_TOKEN=supersecret123 MARIADB_PASSWORD=dbpassword456 CF_DNS_API_TOKEN=cloudflare_token_789 ``` Avantages : - Aucun secret en clair dans les fichiers Compose versionnés - Fichiers `.env` générés dynamiquement par Ansible (templates Jinja2) - Rotation facile des secrets ### 4. Double exposition : local et production Chaque service a deux entrées : - **Local** : `service.local.tellserv.fr` (réseau local uniquement) - **Production** : `service.tellserv.fr` (accessible depuis Internet) ```yaml labels: - "traefik.http.routers.myapp-local.rule=Host(`myapp.local.tellserv.fr`)" - "traefik.http.routers.myapp-local.entryPoints=local" - "traefik.http.routers.myapp-prod.rule=Host(`myapp.tellserv.fr`)" - "traefik.http.routers.myapp-prod.entryPoints=websecure" ``` Avantages : - Accès rapide en local (pas de latence Internet) - Accès à distance possible quand nécessaire - Possibilité de restreindre certains services au local uniquement ### 5. Restart policies et graceful shutdown Configuration de la résilience : ```yaml restart: unless-stopped stop_grace_period: 10s ``` - `unless-stopped` : Redémarre automatiquement sauf si arrêt manuel - `stop_grace_period` : Temps pour terminer proprement avant SIGKILL ### 6. Watchtower pour le monitoring des mises à jour Label pour activer le monitoring : ```yaml labels: - "com.centurylinklabs.watchtower.enable=true" ``` **Important** : Watchtower est utilisé **uniquement pour notifier** des nouvelles versions d'images disponibles. Les mises à jour sont effectuées **manuellement** pour garder le contrôle sur les changements. Dans le [Futur Homelab](../homelab-futur/index.md), la gestion automatisée des mises à jour sera implémentée via Renovate Bot directement intégré à Forgejo. ## Gestion des stacks avec Docker Compose ### Commandes essentielles ```bash # Démarrer tous les services docker compose up -d # Arrêter tous les services docker compose down # Voir les logs d'un service docker compose logs -f service_name # Redémarrer un service docker compose restart service_name # Mettre à jour les images et redéployer docker compose pull docker compose up -d # Voir l'état des conteneurs docker compose ps ``` ### Déploiement via Ansible Dans ma configuration, les stacks sont déployées automatiquement par Ansible : 1. Génération des fichiers `.env` depuis les templates 2. Synchronisation des dossiers `stacks/` vers `/opt/stacks/` 3. Exécution de `docker compose up -d` pour chaque stack Voir la page [Playbooks Ansible](./playbooks-ansible.md) pour plus de détails. ## Avantages de Docker Compose pour un homelab ### Simplicité - Fichiers YAML lisibles et maintenables - Pas de syntaxe complexe comme Kubernetes - Courbe d'apprentissage douce ### Performance - Démarrage instantané des services - Faible overhead (pas de cluster Kubernetes) - Idéal pour des machines modestes ### Flexibilité - Facile d'ajouter/retirer des services - Possibilité de tester rapidement de nouvelles applications - Configuration par environnement (dev, staging, prod) ### Écosystème riche - Docker Hub : des milliers d'images prêtes à l'emploi - LinuxServer.io : images optimisées et bien maintenues - Communauté active : documentation et support ## Limitations de Docker Compose Malgré ses avantages, Docker Compose a des limitations pour un usage production à grande échelle : 1. **Pas de haute disponibilité** : Tout est sur une seule machine 2. **Pas de scaling horizontal** : Impossible de répartir la charge sur plusieurs serveurs 3. **Pas d'orchestration avancée** : Pas de rolling updates, canary deployments, etc. 4. **Gestion manuelle** : Déploiements via Ansible, pas de GitOps natif **Note** : L'utilisation de `restart: unless-stopped` assure le redémarrage automatique des conteneurs après un arrêt inattendu, offrant une forme basique de résilience. Ces limitations expliquent pourquoi je migre vers **Kubernetes (K3S)** pour le futur homelab. Voir la section [Futur Homelab](../homelab-futur/index.md). ## Pourquoi pas Docker Swarm ? Lors de la réflexion sur l'évolution de mon infrastructure, **Docker Swarm** a été considéré comme une alternative à Kubernetes pour l'orchestration de conteneurs. ### Docker Swarm : un choix tentant mais dépassé **Avantages de Docker Swarm** : - Intégré nativement à Docker (pas d'installation supplémentaire) - Configuration plus simple que Kubernetes - Courbe d'apprentissage plus douce - Utilise directement les fichiers Docker Compose (avec quelques adaptations) - Moins gourmand en ressources que Kubernetes **Pourquoi je ne l'ai pas choisi** : 1. **Kubernetes est le standard de l'industrie** : La grande majorité des entreprises utilisent Kubernetes en production. Apprendre K8S offre des compétences directement transférables au monde professionnel. 2. **Écosystème et communauté** : Kubernetes bénéficie d'un écosystème beaucoup plus riche (Helm, operators, nombreux outils DevOps) et d'une communauté bien plus large. 3. **Fonctionnalités avancées** : Kubernetes offre des capacités que Docker Swarm ne possède pas : - Rolling updates et rollbacks plus avancés - Gestion fine des ressources (CPU/RAM limits, requests) - Politiques réseau (Network Policies) plus élaborées - Support natif du GitOps (ArgoCD, Flux) - Stockage distribué mieux intégré (CSI drivers) 4. **Évolution et support** : Docker Inc. a clairement orienté son développement vers Kubernetes plutôt que Swarm. Swarm est maintenu, mais n'évolue plus beaucoup. 5. **Objectif d'apprentissage** : Mon but étant d'acquérir des compétences DevOps modernes, maîtriser Kubernetes est un meilleur investissement à long terme. **Conclusion** : Bien que Docker Swarm soit plus simple et suffisant pour de nombreux homelabs, j'ai préféré investir directement dans l'apprentissage de Kubernetes, qui est devenu le standard incontournable de l'orchestration de conteneurs.