Pour suivre ce tutoriel, il vous faut un minimum de connaissance en:

  • DNS
  • Linux
  • Docker
  • Docker-compose

Provisionner le serveur

Dans mon cas je suis partie sur un serveur dédié chez OVH. Un Kimsufi:

  • Modèle: KS-LE-C
  • CPU: Intel Xeon E5-1620v2 - 4c/8t - 3.7 GHz/3.9 GHz
  • RAM: 32 Go ECC 1600 MHz
  • Stockage: 2×480 Go SSD SATA en Soft RAID

Pour le système d’exploitation, j’ai décidé de partir sur une LTS d’Ubuntu Server (Ubuntu Server 24.04 “Noble Numbat” LTS). J’ai donc la dernière version du système d’exploitation sur une version stable. Je peux donc faire les mises à jour de sécurité pendant 5 ans de mon serveur avant de faire une mise à niveau. Choisissez un data center le plus proche de vos clients, à voir en fonction des disponibilités.

Notez bien l’adresse IP de votre serveur afin de vous connecter plus tard dessus. Si vous pouvez, liez votre nom de domaine à l’adresse IP de votre serveur. Cela vous permettra de connecter directement en faisant:

ssh <utilisateur>@<nom de domaine>

au lieu de :

ssh <utilisateur>@<adresse ip>

Vous allez souvent recevoir par email les premières informations de votre serveur comme l’adresse IP, le premier mot de passe, le nom de l’utilisateur par défaut.

Créer un utilisateur avec moins de droits

Afin de ne pas tous le temps se connecter avec un utilisateur qui a des droits sudo sur le serveur et minimiser la surface potentielle d’attaque, nous allons créer un nouvel utilisateur avec moins de droits.

Création de l’utilisateur

sudo adduser <username>

Rentrer le mot de passe pour cet utilisateur (même si nous allons l’utiliser 1 ou 2 fois par la suite). Garder bien en tête ce nom d’utilisateur, vous allez vous connecter avec plus tard.

Créer le groupe: docker et ajouter l’utilisateur

Afin d’éviter de devoir tout le temps mettre sudodevant nos commandes Docker, nous allons ajouter notre utilisateur à un groupe que nous allons créer.

sudo groupadd docker
sudo usermod -aG docker <username>

Attention

Si vous êtes sur une machine virtuelle ou un VPS, il généralement conseillé de redémarrer entièrement le serveur, pour que les changements prennent effet.

Copie de votre clé SSH

Nous allons maintenant copier la clé SSH sur le serveur afin de pouvoir nous connecter sans mot de passe par la suite.

Information

Si vous n’avez pas encore de clé SSH, voici un guide pour en créer une

ssh-copy-id -i ~/.ssh/<votre clé> <username>@<votre serveur>

Je vous conseille de copier votre clé pour les 2 utilisateurs. Une bonne pratique est de mettre deux clés différentes. Une que vous allez utiliser tous les jours avec l’utilisateur que vous venez de créer et une seconde de secours avec l’utilisateur principale de votre serveur.

Information

Vous pouvez définir à SSH la clé que vous voulez utiliser lors de la connexion

ssh -i ~/.ssh/mykey user@host

Autoriser uniquement la connexion par clé SSH

De nombreux utilisateurs de serveurs SSH utilisent des mots de passe faibles, de nombreux pirates informatiques recherchent des serveurs SSH et devinent des mots de passe au hasard. Un pirate peut essayer des milliers de mots de passe par heure et avec du temps finir par le cracker. La solution recommandée est d’utiliser des clés SSH plutôt que des mots de passe. Pour être aussi difficile à deviner qu’une clé SSH classique, un mot de passe doit contenir 634 lettres et chiffres aléatoires.

Pour désactiver la connexion par mot de passe, nous allons modifier la configuration de notre serveur SSH.

Attention

Vérifier que vous pouvez bien vous connecter avec uniquement votre clé SSH avant d’aller plus loin. Vous risquez de rester bloqué en dehors de votre serveur et de devoir le réinitialiser à zéro.

  1. Ouvrer avec votre éditeur de texte préféré (nano, vi, vim, …) le fichier: /etc/ssh/sshd_config
  2. Modifier la ligne #PasswordAuthentication yes à `PasswordAuthentication n
  3. Enregistrer et redémarrer le service ssh :
sudo service ssh restart

Configurer le pare-feu

Ubuntu fourni un firewall lors de l’installation: Uncomplicated Firewall (UFW). Celui ci va nous permettre de bloquer et/ou autoriser des connexions entrantes comme sortantes.

Attention

Un fonctionnalité de Docker fait que UFW ne contrôle pas les connexions sur les ports définis par les conteneurs. Dans notre cas, ce n’est pas un problème car tous notre trafic va passer par le conteneur de Traefik. Je vous conseil quand même d’appliquer le fix décrit dans cet article: Appliquer les règles UFW à Docker

Dans un premier temps, nous allons vérifier que UFW contrôle les connexions IPv4 comme IPv6. Ouvrez le fichier /etc/default/ufw avec votre éditeur préféré et vérifiez que l’IPV6 soit à vrai: IPV6=yes.

Par la suite, nous allons créer les règles par défaut du serveur.

sudo ufw default deny incoming

Ici nous bloquons par défaut toutes les connexions entrantes vers notre serveur.

De même, nous allons bloquer toutes les connexions sortantes du serveur:

sudo ufw default allow outgoing

Maintenant, on peut créer les règles que l’on souhaite. On va donc ouvrir les ports:

  • 22: pour les connexions ssh
  • 80: pour les connexions http
  • 443: pour les connexions https
sudo ufw allow 22
sudo ufw allow 80
sudo ufw allow 443

Information

Si vous souhaitez vérifier les règles avant d’activer UFW, vous pouvez utiliser la commande : sudo ufw show added

Nos règles implémentés, on va pour maintenant activer UFW.

sudo ufw enable

Le terminal va vous signaler que les connexions SSH actives risquent d’être coupées. Confirmez avec la touche Y.

Félicitation, le firewall est actif!

Installer les outils de base

Docker

La première étape est de rajouter le repository de Docker pour l’installer avec apt:

# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install 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
 
# Add the repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

Puis d’installer la dernière version de Docker:

 sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Docker est installé!

Pour finir, nous allons configurer le stockage de logs de Docker.

Pour cela il suffit de modifier le fichier /etc/docker/daemon.json:

{
  "log-driver": "local",
  "log-opts": {
    "max-size": "20m",
    "max-file": "14"
  }
}

Ici, on garde le driver par défaut de Docker. On ajoute juste 2 options:

  • Le fichier de log ne peux pas faire plus de 20 Mo.
  • Il ne peux pas avoir plus de 14 fichiers de log.

Dans mon cas, je générais entre 8 et 16Mo de log par jour. Cette configuration me permet donc de garder un peu plus de 14 jours de log.

Traefik

Traefik est un proxy inverse et un load balancer open-source conçu pour faciliter la gestion du trafic vers des microservices et des conteneurs. Il s’intègre nativement avec des orchestrateurs comme Docker, Kubernetes et Consul, et permet l’auto-découverte des services. Grâce à son support intégré pour le HTTPS (via Let’s Encrypt), le routage dynamique et sa configuration déclarative.

Ici, je vais donner le fichier docker-compose.yaml puis revenir ligne par ligne dessus:

services:
  traefik:
    container_name: traefik
    image: traefik:v3.1
    restart: unless-stopped
    secrets:
      - traefik-login
    command:
      - "--providers.docker"
      - "--providers.docker.exposedbydefault=false"
      - "--entryPoints.websecure.address=:443"
      - "--certificatesresolvers.leresolver.acme.tlschallenge=true"
      - "--certificatesresolvers.leresolver.acme.email=<votre adresse email>"
      - "--certificatesresolvers.leresolver.acme.storage=/letsencrypt/acme.json"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--api.dashboard=true"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - letsencrypt:/letsencrypt
      - /var/run/docker.sock:/var/run/docker.sock
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik.rule=Host(`traefik.<nom de domaine>`)"
      - "traefik.http.routers.traefik.entrypoints=websecure"
      - "traefik.http.routers.traefik.tls.certresolver=leresolver"
      - "traefik.http.middlewares.auth.basicauth.usersfile=/run/secrets/traefik-login"
      - "traefik.http.routers.traefik.middlewares=auth"
      - "traefik.http.routers.traefik.service=api@internal"
 
volumes:
  letsencrypt:
 
secrets:
  traefik-login:
    file: secrets/app/internal/traefik-login.txt
traefik:
  container_name: traefik
  image: traefik:v3.1
  restart: unless-stopped

Dans un premier temps, on créer un service avec l’image de Traefik. On défini les stratégies de redémarrage du service. Ici, le service redémarrera sauf si l’utilisateur le stop.

	command:
      - "--providers.docker"
      - "--providers.docker.exposedbydefault=false"
      - "--certificatesresolvers.leresolver.acme.tlschallenge=true"
      - "--certificatesresolvers.leresolver.acme.email=<votre adresse email>"
      - "--certificatesresolvers.leresolver.acme.storage=/letsencrypt/acme.json"
    volumes:
      - letsencrypt:/letsencrypt
      - /var/run/docker.sock:/var/run/docker.sock
 
volumes:
  letsencrypt:

Ensuite, on sélectionne le provider de Traefik, dans notre cas: docker et on monte le socket de docker avec le volume. De plus, on monte un volume qui sert à stocker les certificats SSL générés par Traefik et on indique à Traefik les informations nécessaires pour LetsEncrypt.

command:
  - "--entrypoints.web.address=:80"
  - "--entryPoints.websecure.address=:443"
  - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
  - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
ports:
  - "80:80"
  - "443:443"

Ici ont créé les entrypoints de Traefik: 80 pour l’HTTP et 443 pour l’HTTPS. À cela on ajoute une condition: transforme toutes les requêtes HTTP en HTTPS.

    secrets:
      - traefik-login
    command:
      - "--api.dashboard=true"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik.rule=Host(`traefik.<nom de domaine>`)"
      - "traefik.http.routers.traefik.entrypoints=websecure"
      - "traefik.http.routers.traefik.tls.certresolver=leresolver"
      - "traefik.http.middlewares.auth.basicauth.usersfile=/run/secrets/traefik-login"
      - "traefik.http.routers.traefik.middlewares=auth"
      - "traefik.http.routers.traefik.service=api@internal"
 
secrets:
  traefik-login:
    file: secrets/app/internal/traefik-login.txt

Enfin, on active l’interface graphique de Traefik. Ici, on va pouvoir surveiller les services, routes et middleware. On lui défini une URL, défini l’entrypoints à utiliser et le gestionnaire de certificat.

Pour éviter que tout le monde puisse avoir accès à ce dashboard, on lui ajoute un mot de passe sous la forme d’un middleware. Pour éviter d’écrire en dure le mot de passe dans le fichier, on utilise les secrets fournis par Docker.

Et voilà! Traefik est opérationnel.

Portainer

Portainer est une interface web simplifiant la gestion des conteneurs Docker, Kubernetes et d’autres environnements. Il offre une gestion intuitive des stacks, volumes, réseaux et images, facilitant l’administration des infrastructures cloud et DevOps.

portainer:
  container_name: portainer
  image: portainer/portainer-ce:alpine
  command: -H unix:///var/run/docker.sock
  restart: unless-stopped
  volumes:
    - /var/run/docker.sock:/var/run/docker.sock
    - portainer:/data
  labels:
    # Frontend
    - "traefik.enable=true"
    - "traefik.http.routers.frontend.rule=Host(`portainer.<nom de domaine>`)"
    - "traefik.http.routers.frontend.entrypoints=websecure"
    - "traefik.http.services.frontend.loadbalancer.server.port=9000"
    - "traefik.http.routers.frontend.service=frontend"
    - "traefik.http.routers.frontend.tls.certresolver=leresolver"
 
    # Edge
    - "traefik.http.routers.edge.rule=Host(`edge.<nom de domaine>`)"
    - "traefik.http.routers.edge.entrypoints=websecure"
    - "traefik.http.services.edge.loadbalancer.server.port=8000"
    - "traefik.http.routers.edge.service=edge"
    - "traefik.http.routers.edge.tls.certresolver=leresolver"
  healthcheck:
    test: wget -q --no-verbose --tries=3 --spider http://127.0.0.1:9000/api/system/status || exit 1
    interval: 10s
    timeout: 5s
    retries: 5
    start_period: 20s

Dans ce nouveau service, on utilise une image de communauté de Portainer en version alpine (plus légère). Comme pour Traefik, on monte le socket de docker et un moyen de sauvegarder les données de configuration de Portainer. Avec les labels, on indique à Traefik comment afficher les différents composants (Frontend et Edge) de Portainer.

Une bonne pratique est d’ajouter un healthcheck. Cela permet à Docker de définir l’état de santé du container. En quoi cela nous intéresse ? Eh bien, Traefik se base sur l’état de santé du container (s’il est défini) pour ouvrir ou non les connexions.

Voilà ! Vous un serveur prêt à accueillir vos plus beaux projets.

Aller plus loin

Source