~/wiki

Cheatsheets — vue longue

retour à la liste

Toutes les pages concaténées sur un seul document, pour un Ctrl-F direct.

Le cycle quotidien

docker build -t monapp:dev .
docker run --rm -p 8000:8000 --env-file .env monapp:dev
docker ps
docker logs -f <container>
docker exec -it <container> bash
docker stop <container>
docker ps
CONTAINER ID   IMAGE        COMMAND         STATUS         PORTS                    NAMES
a3f1c9e21b04   monapp:dev   "uvicorn ap…"   Up 2 minutes   0.0.0.0:8000->8000/tcp   brave_liskov

docker ps -a inclut les conteneurs arrêtés. --rm supprime le conteneur à sa sortie, ce qui évite d'en accumuler des dizaines.

Flags de run à connaître

Flag Effet
-p 8000:8000 port hôte:conteneur
-v $(pwd):/app monte le dossier courant, code à chaud
--env-file .env charge les variables
-e KEY=value une variable à la volée
-d détaché, en arrière-plan
-it interactif + tty, pour un shell
--rm nettoie à la sortie
--name api nom fixe au lieu d'un nom généré

Dockerfile Python typique

FROM python:3.12-slim

WORKDIR /app

# Couche de dépendances séparée : elle n'est reconstruite que si les
# requirements changent, pas à chaque édition du code.
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

--host 0.0.0.0 est obligatoire : sur 127.0.0.1 le serveur n'écoute que l'intérieur du conteneur et le port publié ne sert à rien.

Un .dockerignore évite d'envoyer 1 Go de contexte au démon :

.venv
node_modules
.git
__pycache__
*.pyc
.env

Compose

services:
  api:
    build: .
    ports: ["8000:8000"]
    env_file: .env
    volumes: ["./app:/app/app"]      # code à chaud en dev
    depends_on: [db]

  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: dev
    ports: ["5432:5432"]
    volumes: ["pgdata:/var/lib/postgresql/data"]

volumes:
  pgdata:
docker compose up              # au premier plan
docker compose up -d --build   # reconstruit puis détache
docker compose logs -f api
docker compose exec api bash
docker compose down            # -v pour supprimer aussi les volumes

--build n'est nécessaire que si le Dockerfile ou les dépendances changent. Avec un volume monté et un reload actif, le code est déjà à jour sans rebuild.

Nettoyer

docker system df               # où part la place
docker system prune -f         # conteneurs et réseaux inutilisés
docker system prune -a -f      # + toutes les images non utilisées
docker builder prune -f        # cache de build
docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          24        3         18.4GB    15.2GB (82%)
Containers      5         1         120MB     98MB (81%)
Local Volumes   7         2         2.1GB     1.4GB (66%)
Build Cache     183       0         9.8GB     9.8GB

Les erreurs qu'on rencontre vraiment

port is already allocated — un autre processus tient le port.

lsof -i :8000
kill -9 $(lsof -t -i :8000)

no space left on device pendant un build — le disque de la VM Docker est plein, faire un prune progressif avant d'envisager un reset.

Le conteneur démarre puis s'arrête aussitôt — le processus principal a rendu la main. docker logs <container> donne toujours la raison.

Build lent à chaque fois — l'ordre des couches est mauvais : COPY . . avant pip install invalide le cache à la moindre modification de code.

Inspecter

docker images
docker inspect <container> | rg -i "ipaddress|mounts" -A 5
docker stats                   # CPU / RAM en direct
docker history monapp:dev      # poids par couche

See also

Infrastructure décrite en fichiers, appliquée de façon idempotente. On décrit l'état voulu, Terraform calcule les opérations pour y arriver.

Le cycle

terraform init       # télécharge les providers, configure le backend
terraform fmt        # reformate le HCL
terraform validate   # vérifie la syntaxe et les références
terraform plan       # ce qui va changer, sans rien faire
terraform apply      # applique après confirmation
terraform destroy    # supprime tout ce qui est géré
terraform plan
Terraform will perform the following actions:

  # aws_s3_bucket.artifacts will be created
  + resource "aws_s3_bucket" "artifacts" {
      + bucket = "monprojet-artifacts"
      + id     = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Toujours lire le plan avant d'appliquer. La ligne à surveiller est le nombre de destroy : un changement anodin peut provoquer un remplacement de ressource.

Structure d'un projet

main.tf         ressources
variables.tf    entrées
outputs.tf      sorties
providers.tf    providers et versions
terraform.tfvars   valeurs (à ne pas committer si secrets)
terraform {
  required_version = ">= 1.9"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"        # >= 5.0, < 6.0
    }
  }
  backend "s3" {
    bucket = "mon-tfstate"
    key    = "prod/terraform.tfstate"
    region = "eu-west-3"
  }
}

provider "aws" {
  region = var.region
}

Le backend distant est le premier réflexe en équipe : sans lui, le fichier d'état vit sur un poste et deux personnes qui appliquent en même temps se marchent dessus.

Variables et sorties

variable "region" {
  type        = string
  default     = "eu-west-3"
  description = "Région AWS"
}

variable "instance_count" {
  type = number
  validation {
    condition     = var.instance_count > 0
    error_message = "Il faut au moins une instance."
  }
}

output "bucket_url" {
  value = aws_s3_bucket.artifacts.bucket_domain_name
}

output "db_password" {
  value     = random_password.db.result
  sensitive = true      # masqué dans les logs
}
terraform apply -var="region=eu-west-1"
terraform apply -var-file="prod.tfvars"
TF_VAR_region=eu-west-1 terraform apply

Ressources et références

resource "aws_s3_bucket" "artifacts" {
  bucket = "${var.project}-artifacts"

  tags = {
    Environment = var.env
    ManagedBy   = "terraform"
  }
}

resource "aws_s3_bucket_versioning" "artifacts" {
  bucket = aws_s3_bucket.artifacts.id     # crée la dépendance implicite
  versioning_configuration {
    status = "Enabled"
  }
}

Le graphe de dépendances se déduit des références. depends_on ne sert que pour les dépendances invisibles.

Boucles et conditions

resource "aws_instance" "worker" {
  count         = var.instance_count
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
  tags = { Name = "worker-${count.index}" }
}

resource "aws_s3_bucket" "per_env" {
  for_each = toset(["dev", "staging", "prod"])
  bucket   = "monprojet-${each.key}"
}

locals {
  is_prod = var.env == "prod"
  size    = local.is_prod ? "t3.large" : "t3.micro"
}

Préférer for_each à count : avec count, supprimer un élément au milieu décale les index et Terraform détruit puis recrée tout ce qui suit.

Data sources

data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]
  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }
}

État

terraform state list
terraform state show aws_s3_bucket.artifacts
terraform import aws_s3_bucket.artifacts mon-bucket-existant
terraform state rm aws_s3_bucket.artifacts     # oublie sans détruire
terraform refresh

import sert à reprendre la main sur une ressource créée à la main dans la console.

Modules

module "network" {
  source = "./modules/network"
  cidr   = "10.0.0.0/16"
}

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.8.1"
  name    = "prod"
}

# module.network.subnet_ids pour lire une sortie

Cibler et forcer

terraform plan -target=aws_s3_bucket.artifacts     # dépannage, pas une habitude
terraform apply -replace=aws_instance.worker[0]    # recrée une ressource
terraform apply -auto-approve                      # CI uniquement

Pièges

Le .tfstate contient des secrets en clair : jamais dans git, toujours dans un backend chiffré. Un terraform destroy sur le mauvais workspace est irréversible. Et le plan n'est valide qu'à l'instant où il est produit — si quelqu'un modifie l'infra à la main entre-temps, l'apply diverge.

See also