vault backup: 2025-12-19 19:08:14
This commit is contained in:
parent
2508a9df0f
commit
0ce4792a80
1 changed files with 438 additions and 30 deletions
|
|
@ -1,17 +1,18 @@
|
|||
---
|
||||
sidebar_position: 3
|
||||
tags: [docker, docker-compose, containerization, homelab]
|
||||
last_update:
|
||||
date: 2025-11-25
|
||||
---
|
||||
|
||||
# Docker and Docker Compose
|
||||
|
||||
:::info
|
||||
Full English translation coming soon.
|
||||
:::
|
||||
|
||||
Docker is a **containerization platform** that allows packaging applications and their dependencies into lightweight and isolated containers.
|
||||
|
||||
## What is Docker?
|
||||
|
||||
Docker is a **containerization platform** that allows you to package applications and their dependencies into lightweight and isolated containers.
|
||||
|
||||
### Containers: revolution of modern infrastructure
|
||||
|
||||
A container is a standardized software unit that contains:
|
||||
- The application itself
|
||||
- All its dependencies (libraries, runtime, system tools)
|
||||
|
|
@ -22,41 +23,452 @@ A container is a standardized software unit that contains:
|
|||
- **Container**: Shares the host OS kernel, starts in seconds, very lightweight (~MB)
|
||||
- **VM**: Emulates a complete OS, starts in minutes, heavier (~GB)
|
||||
|
||||
## Docker Compose: Simplified orchestration
|
||||
### Advantages of Docker
|
||||
|
||||
1. **Portability**: "Runs anywhere" - works identically in development, testing, and production
|
||||
2. **Isolation**: Each container is isolated, avoiding dependency conflicts
|
||||
3. **Lightweight**: Consumes fewer resources than a VM (no full virtualization)
|
||||
4. **Speed**: Instant application startup
|
||||
5. **Reproducibility**: Docker image = identical environment every time
|
||||
6. **Ecosystem**: Docker Hub contains thousands of ready-to-use images
|
||||
|
||||
## Docker Compose: simplified orchestration
|
||||
|
||||
Docker Compose is an **orchestration tool** for defining and managing multi-container applications.
|
||||
|
||||
### Why Docker Compose?
|
||||
|
||||
- **Declarative configuration**: Everything defined in a `compose.yml` file
|
||||
Without Compose, deploying an application with multiple containers (app + database + cache + ...) requires long `docker run` commands that are difficult to maintain.
|
||||
|
||||
With Compose:
|
||||
- **Declarative configuration**: Everything is defined in a `compose.yml` file
|
||||
- **Grouped management**: Start/stop all services with one command
|
||||
- **Automatic networks**: Containers communicate easily between them
|
||||
- **Automatic networks**: Containers communicate easily with each other
|
||||
- **Persistent volumes**: Simple storage management
|
||||
- **Environment variables**: Flexible configuration via `.env` files
|
||||
|
||||
## Configuration examples
|
||||
### compose.yml file
|
||||
|
||||
My Docker Compose stacks are available in the Ansible repository under `stacks/`. Key examples include:
|
||||
- **Traefik**: Advanced reverse proxy with two instances (public and private)
|
||||
- **Photoprism**: Application with database (app + DB)
|
||||
- **Mobilizon**: Multi-container application with multiple networks
|
||||
- **Vaultwarden**: Security-focused configuration
|
||||
A Compose file defines:
|
||||
- **Services** (containers)
|
||||
- **Networks** (communication between containers)
|
||||
- **Volumes** (data persistence)
|
||||
- **Environment variables** (configuration)
|
||||
|
||||
## Docker Compose stack examples
|
||||
|
||||
My Docker Compose stacks are available in the Ansible repository under `stacks/`. Here are some representative examples:
|
||||
|
||||
### Example 1: Traefik - Advanced Reverse Proxy
|
||||
|
||||
Traefik is the entry point for the entire infrastructure. This compose illustrates an advanced configuration with **two Traefik instances** (public and private):
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
**Key points**:
|
||||
- **Two instances**: Separation of public (Internet) and private (local network only)
|
||||
- **Docker socket**: Traefik automatically detects new containers via `/var/run/docker.sock`
|
||||
- **Let's Encrypt certificates**: Automatic generation with DNS-01 challenge (Cloudflare)
|
||||
- **Traefik labels**: Dynamic configuration via Docker labels
|
||||
- **CrowdSec middleware**: Integration with CrowdSec to block malicious IPs
|
||||
- **External network**: All services connect to the `traefik_network` network
|
||||
|
||||
### Example 2: Photoprism - Application with database
|
||||
|
||||
Photoprism illustrates a classic application stack (app + DB) with advanced configuration:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
**Key points**:
|
||||
- **Dependencies**: `depends_on` ensures MariaDB starts before Photoprism
|
||||
- **Mounted volumes**: Access to MergerFS storage (`/mnt/storage`) for photos
|
||||
- **Database**: MariaDB optimized for Photoprism (buffer pool, character set UTF-8)
|
||||
- **Environment variables**: Secrets injected via `.env` file (not versioned)
|
||||
- **Dual exposure**: Accessible locally (`.local.tellserv.fr`) and on the Internet (`.tellserv.fr`)
|
||||
- **Watchtower**: Label to enable automatic updates
|
||||
- **DB optimizations**: Adapted MariaDB configuration (buffer pool, connections, charset)
|
||||
|
||||
### Example 3: Mobilizon - Multi-container application with internal network
|
||||
|
||||
Mobilizon demonstrates the use of **multiple Docker networks** (external + internal):
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
**Key points**:
|
||||
- **Two networks**:
|
||||
- `traefik_network` (external): Mobilizon communicates with Traefik
|
||||
- `mobilizon_internal` (internal): Private communication between Mobilizon and PostgreSQL
|
||||
- **Security**: The database is not exposed on the Traefik network
|
||||
- **PostgreSQL with PostGIS**: Geographic extension to manage geolocated events
|
||||
- **User ID**: Execution with specific UID/GID to manage file permissions
|
||||
- **Volume with SELinux**: Flag `:z` for SELinux compatibility
|
||||
|
||||
### Example 4: Vaultwarden - Secrets management
|
||||
|
||||
Vaultwarden (password manager) shows a security-focused configuration:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
**Key points**:
|
||||
- **Secrets via .env**: All passwords and tokens in environment variables
|
||||
- **SMTP configuration**: Email sending for notifications and account recovery
|
||||
- **Experimental features**: SSH key support in the vault
|
||||
- **Data volume**: Vault persistence in `./vw-data`
|
||||
- **Secure exposure**: HTTPS mandatory via Traefik with Let's Encrypt
|
||||
|
||||
## Patterns and best practices
|
||||
|
||||
1. **External network `traefik_network`**: All services share a common Docker network
|
||||
2. **Traefik labels**: Dynamic configuration via Docker labels
|
||||
3. **Environment variables with .env files**: Secrets extracted from Compose files
|
||||
4. **Dual exposure**: local and production access for each service
|
||||
5. **Restart policies**: `unless-stopped` for resilience
|
||||
6. **Watchtower for monitoring**: Watchtower is used **only for notifications** of available image updates. Updates are performed **manually** to maintain control over changes. In the [Future Homelab](../homelab-futur/index.md), automated update management will be implemented via Renovate Bot integrated directly with Forgejo.
|
||||
### 1. External network `traefik_network`
|
||||
|
||||
## Benefits for a homelab
|
||||
All my services use a shared **external Docker network**:
|
||||
|
||||
- **Simplicity**: Readable and maintainable YAML files
|
||||
- **Performance**: Instant service startup, low overhead
|
||||
- **Flexibility**: Easy to add/remove services
|
||||
- **Rich ecosystem**: Docker Hub with thousands of ready-to-use images
|
||||
```yaml
|
||||
networks:
|
||||
traefik_network:
|
||||
external: true
|
||||
```
|
||||
|
||||
Advantages:
|
||||
- Traefik automatically detects new services
|
||||
- Communication between services via their names (e.g. `http://vaultwarden`)
|
||||
- Isolation by default (unconnected services cannot communicate)
|
||||
|
||||
### 2. Traefik labels for dynamic configuration
|
||||
|
||||
Instead of static configuration files, I use **Docker labels**:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.myapp.rule=Host(`myapp.tellserv.fr`)"
|
||||
- "traefik.http.routers.myapp.tls.certResolver=cloudflare"
|
||||
```
|
||||
|
||||
Advantages:
|
||||
- Configuration colocated with the service
|
||||
- Deploying a new service = automatic addition to Traefik
|
||||
- No manual Traefik reload
|
||||
|
||||
### 3. Environment variables with .env files
|
||||
|
||||
All secrets are extracted into `.env` files:
|
||||
|
||||
```env
|
||||
VAULTWARDEN_ADMIN_TOKEN=supersecret123
|
||||
MARIADB_PASSWORD=dbpassword456
|
||||
CF_DNS_API_TOKEN=cloudflare_token_789
|
||||
```
|
||||
|
||||
Advantages:
|
||||
- No secrets in plain text in versioned Compose files
|
||||
- `.env` files generated dynamically by Ansible (Jinja2 templates)
|
||||
- Easy secret rotation
|
||||
|
||||
### 4. Dual exposure: local and production
|
||||
|
||||
Each service has two entries:
|
||||
- **Local**: `service.local.tellserv.fr` (local network only)
|
||||
- **Production**: `service.tellserv.fr` (accessible from the 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"
|
||||
```
|
||||
|
||||
Advantages:
|
||||
- Fast local access (no Internet latency)
|
||||
- Remote access possible when needed
|
||||
- Ability to restrict certain services to local only
|
||||
|
||||
### 5. Restart policies and graceful shutdown
|
||||
|
||||
Resilience configuration:
|
||||
|
||||
```yaml
|
||||
restart: unless-stopped
|
||||
stop_grace_period: 10s
|
||||
```
|
||||
|
||||
- `unless-stopped`: Restarts automatically except if manually stopped
|
||||
- `stop_grace_period`: Time to terminate cleanly before SIGKILL
|
||||
|
||||
### 6. Watchtower for update monitoring
|
||||
|
||||
Label to enable monitoring:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "com.centurylinklabs.watchtower.enable=true"
|
||||
```
|
||||
|
||||
**Important**: Watchtower is used **only for notifications** of new available image versions. Updates are performed **manually** to maintain control over changes.
|
||||
|
||||
In the [Future Homelab](../homelab-futur/index.md), automated update management will be implemented via Renovate Bot integrated directly with Forgejo.
|
||||
|
||||
## Managing stacks with Docker Compose
|
||||
|
||||
### Essential commands
|
||||
|
||||
```bash
|
||||
# Start all services
|
||||
docker compose up -d
|
||||
|
||||
# Stop all services
|
||||
docker compose down
|
||||
|
||||
# View logs for a service
|
||||
docker compose logs -f service_name
|
||||
|
||||
# Restart a service
|
||||
docker compose restart service_name
|
||||
|
||||
# Update images and redeploy
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
|
||||
# View container status
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
### Deployment via Ansible
|
||||
|
||||
In my configuration, stacks are automatically deployed by Ansible:
|
||||
|
||||
1. Generation of `.env` files from templates
|
||||
2. Synchronization of `stacks/` folders to `/opt/stacks/`
|
||||
3. Execution of `docker compose up -d` for each stack
|
||||
|
||||
See the [Ansible Playbooks](./playbooks-ansible.md) page for more details.
|
||||
|
||||
## Advantages of Docker Compose for a homelab
|
||||
|
||||
### Simplicity
|
||||
|
||||
- Readable and maintainable YAML files
|
||||
- No complex syntax like Kubernetes
|
||||
- Gentle learning curve
|
||||
|
||||
### Performance
|
||||
|
||||
- Instant service startup
|
||||
- Low overhead (no Kubernetes cluster)
|
||||
- Ideal for modest machines
|
||||
|
||||
### Flexibility
|
||||
|
||||
- Easy to add/remove services
|
||||
- Ability to quickly test new applications
|
||||
- Configuration by environment (dev, staging, prod)
|
||||
|
||||
### Rich ecosystem
|
||||
|
||||
- Docker Hub: thousands of ready-to-use images
|
||||
- LinuxServer.io: optimized and well-maintained images
|
||||
- Active community: documentation and support
|
||||
|
||||
## Docker Compose limitations
|
||||
|
||||
Despite its advantages, Docker Compose has limitations for large-scale production use:
|
||||
|
||||
1. **No high availability**: Everything is on a single machine
|
||||
2. **No horizontal scaling**: Impossible to distribute load across multiple servers
|
||||
3. **No advanced orchestration**: No rolling updates, canary deployments, etc.
|
||||
4. **Manual management**: Deployments via Ansible, no native GitOps
|
||||
|
||||
**Note**: Using `restart: unless-stopped` ensures automatic restart of containers after an unexpected stop, providing a basic form of resilience.
|
||||
|
||||
These limitations explain why I'm migrating to **Kubernetes (K3S)** for the future homelab. See the [Future Homelab](../homelab-futur/index.md) section.
|
||||
|
||||
## Why not Docker Swarm?
|
||||
|
||||
|
|
@ -89,7 +501,3 @@ When considering the evolution of my infrastructure, **Docker Swarm** was evalua
|
|||
5. **Learning objective**: My goal being to acquire modern DevOps skills, mastering Kubernetes is a better long-term investment.
|
||||
|
||||
**Conclusion**: Although Docker Swarm is simpler and sufficient for many homelabs, I preferred to invest directly in learning Kubernetes, which has become the essential standard for container orchestration.
|
||||
|
||||
:::note
|
||||
Detailed English translation of this page is in progress.
|
||||
:::
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue