Skip to content

Installer KART avec Docker (from scratch)


Sommaire


Prérequis

Plateforme cible

  • VPS Linux (Ubuntu/Debian conseillés)
  • Docker Engine ≥ 24 et Docker Compose v2 (docker compose version)
  • Un utilisateur non-root membre du groupe docker
  • (Optionnel) Un nom de domaine pointant vers l’IP du VPS
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg       | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo       "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg]       https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")       $(. /etc/os-release; echo "$VERSION_CODENAME") stable"       | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker $USER
# reconnecter votre session

Arborescence du dépôt

Placez ces fichiers à la racine du projet (là où se trouve manage.py).

KART/
├─ manage.py
├─ kart/                     # package Django (settings/urls/wsgi/…)
├─ requirements.txt          # ou pyproject.toml si Poetry
├─ Dockerfile
├─ docker-compose.yml
├─ entrypoint.sh
├─ nginx/
│  └─ nginx.conf
├─ .env.example
└─ docs/installation-docker.md   # cette page (facultatif)

Variables d’environnement

Créez votre fichier .env à partir du modèle ci-dessous.

.env.example

# Django
DJANGO_SETTINGS_MODULE=kart.settings
DJANGO_DEBUG=0
DJANGO_SECRET_KEY=CHANGE_ME
ALLOWED_HOSTS=localhost,127.0.0.1,example.com
CSRF_TRUSTED_ORIGINS=https://example.com

# Base de données
POSTGRES_DB=kart
POSTGRES_USER=kart
POSTGRES_PASSWORD=change_me
POSTGRES_HOST=db
POSTGRES_PORT=5432
DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}

# Redis
REDIS_URL=redis://redis:6379/0

# App / Gunicorn
WEB_CONCURRENCY=3
BIND=0.0.0.0:8000
DJANGO_STATIC_URL=/static/
DJANGO_MEDIA_URL=/media/
TZ=Europe/Paris

# Celery
CELERY_BROKER_URL=${REDIS_URL}
CELERY_RESULT_BACKEND=${REDIS_URL}

# Nginx / Domaine
SERVER_NAME=example.com

python - <<'PY'
import secrets,string
print(''.join(secrets.choice(string.ascii_letters+string.digits+'!@#$%^&*(-_=+)') for _ in range(64)))
PY

Fichiers clés

Dockerfile

Dockerfile

# syntax=docker/dockerfile:1.7
FROM python:3.12-slim AS base

ENV PYTHONDONTWRITEBYTECODE=1     PYTHONUNBUFFERED=1     PIP_NO_CACHE_DIR=1     PATH="/usr/local/bin:$PATH"

RUN apt-get update && apt-get install -y --no-install-recommends     build-essential gcc     libpq-dev     libjpeg62-turbo-dev zlib1g-dev libpng-dev     curl   && rm -rf /var/lib/apt/lists/*

WORKDIR /app

COPY requirements.txt /app/requirements.txt
RUN pip install --upgrade pip && pip install -r requirements.txt

COPY . /app

RUN useradd --create-home --shell /bin/bash appuser && chown -R appuser:appuser /app
USER appuser

COPY --chmod=755 entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

CMD ["gunicorn", "kart.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "3", "--timeout", "60"]


Entrypoint

entrypoint.sh

#!/usr/bin/env bash
set -euo pipefail

# Charger .env si présent
if [ -f ".env" ]; then
  export $(grep -v '^#' .env | xargs)
fi

python manage.py migrate --noinput

# Collecte des statiques si DEBUG=0
if [ "${DJANGO_DEBUG:-0}" = "0" ]; then
  python manage.py collectstatic --noinput
fi

# Lancer la commande (gunicorn par défaut)
exec "$@"


docker-compose

docker-compose.yml

version: "3.9"

x-env: &default-env
  env_file: .env
  restart: unless-stopped

services:
  db:
    image: postgres:16-alpine
    <<: *default-env
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      TZ: ${TZ}
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 10

  redis:
    image: redis:7-alpine
    <<: *default-env
    command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"]
    volumes:
      - redisdata:/data

  web:
    build:
      context: .
      dockerfile: Dockerfile
    <<: *default-env
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    command: ["gunicorn", "kart.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "${WEB_CONCURRENCY}", "--timeout", "60"]
    ports:
      - "8000:8000"   # utile en dev; en prod on passe via nginx:80
    volumes:
      - static:/app/staticfiles
      - media:/app/media

  worker:
    build: .
    <<: *default-env
    depends_on:
      redis:
        condition: service_started
      db:
        condition: service_healthy
    command: ["celery", "-A", "kart", "worker", "--loglevel=INFO"]
    volumes:
      - media:/app/media

  beat:
    build: .
    <<: *default-env
    depends_on:
      redis:
        condition: service_started
      db:
        condition: service_healthy
    command: ["celery", "-A", "kart", "beat", "--loglevel=INFO"]

  nginx:
    image: nginx:1.27-alpine
    <<: *default-env
    depends_on:
      - web
    ports:
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - static:/vol/static:ro
      - media:/vol/media:ro

volumes:
  pgdata:
  redisdata:
  static:
  media:


Nginx (reverse proxy)

nginx/nginx.conf

worker_processes auto;
events { worker_connections 1024; }

http {
  include       mime.types;
  sendfile      on;
  server_tokens off;
  client_max_body_size 25m;

  upstream kart_app { server web:8000; }

  server {
    listen 80;
    server_name ${SERVER_NAME};

    location /static/ {
      alias /vol/static/;
      access_log off;
      expires 7d;
    }

    location /media/ {
      alias /vol/media/;
      access_log off;
      expires 7d;
    }

    location / {
      proxy_pass http://kart_app;
      proxy_set_header Host $host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_set_header X-Forwarded-Host $host;
      proxy_redirect off;
    }
  }
}


Mise en route

  1. Préparer l’environnement

    cp .env.example .env
    # Éditez .env (ALLOWED_HOSTS, SERVER_NAME, mots de passe…)
    

  2. Construire et démarrer

    docker compose build
    docker compose up -d db redis
    docker compose up -d web
    

  3. Créer un superuser

    docker compose exec web python manage.py createsuperuser
    

  4. (Optionnel) Celery & Nginx

    docker compose up -d worker beat
    docker compose up -d nginx
    

  5. Appli : http://<IP_VPS> (ou http://localhost en local)

  6. Admin : http://<IP_VPS>/admin/

HTTPS (optionnel)

docker-compose.yml (extrait)
Remplacez nginx par caddy :

caddy:
  image: caddy:2-alpine
  env_file: .env
  restart: unless-stopped
  depends_on: [web]
  ports: ["80:80", "443:443"]
  volumes:
    - caddydata:/data
    - static:/srv/static:ro
    - media:/srv/media:ro
  environment:
    SERVER_NAME: ${SERVER_NAME}
  command: |
    caddy run --config /etc/caddy/Caddyfile
  configs:
    - source: caddyfile
      target: /etc/caddy/Caddyfile

volumes:
  caddydata:
configs:
  caddyfile:
    file: ./Caddyfile

Caddyfile

{$SERVER_NAME}

encode zstd gzip
log

handle_path /static/* {
  root * /srv/static
  file_server
}

handle_path /media/* {
  root * /srv/media
  file_server
}

reverse_proxy web:8000

Conservez le service nginx et suivez un guide certbot en conteneur (non détaillé ici).
Veillez à monter les certificats dans /etc/nginx/conf.d/ssl et à activer le listen 443 ssl;.


Mises à jour / déploiements

# Mettre à jour le code
git pull

# Rebuild si Dockerfile/requirements ont changé
docker compose build
docker compose up -d

# Migrations & statiques (si nécessaire)
docker compose exec web python manage.py migrate --noinput
docker compose exec web python manage.py collectstatic --noinput

Sauvegardes

Base Postgres

# Sauvegarde :
docker compose exec -T db pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -F c > backup_$(date +%F).dump

# Restauration :
cat backup_YYYY-MM-DD.dump | docker compose exec -T db pg_restore -U "$POSTGRES_USER" -d "$POSTGRES_DB" --clean --if-exists

Médias

docker run --rm -v $(pwd):/pwd -v $(pwd)/media:/media alpine   sh -c "cd /media && tar czf /pwd/media_$(date +%F).tar.gz ."

Paramètres Django à vérifier

# settings.py
DEBUG = bool(int(os.getenv("DJANGO_DEBUG", 0)))
ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "").split(",")
CSRF_TRUSTED_ORIGINS = [os.getenv("CSRF_TRUSTED_ORIGINS", "")] if os.getenv("CSRF_TRUSTED_ORIGINS") else []

STATIC_URL = os.getenv("DJANGO_STATIC_URL", "/static/")
STATIC_ROOT = BASE_DIR / "staticfiles"
MEDIA_URL = os.getenv("DJANGO_MEDIA_URL", "/media/")
MEDIA_ROOT = BASE_DIR / "media"

SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 60 * 60 * 24 * 30
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True

Dépannage

Page blanche / 502 via Nginx
  • Vérifiez que le service web tourne : docker compose ps
  • proxy_pass pointe-t-il vers web:8000 ?
  • Logs : docker compose logs -f nginx web
Statiques en 404
  • collectstatic exécuté ?
  • Volume static: monté correctement dans web et nginx ?
DB : could not connect to server
  • Attendre la healthcheck Postgres (compose relance automatiquement).
  • Redémarrer web une fois db healthy.
CSRF en HTTPS
  • Ajouter le domaine à ALLOWED_HOSTS
  • CSRF_TRUSTED_ORIGINS doit inclure le schéma : https://example.com
Celery ne consomme pas
  • CELERY_BROKER_URL/CELERY_RESULT_BACKEND pointent sur redis
  • Logs : docker compose logs -f worker beat

Checklist Prod

  • [ ] DJANGO_DEBUG=0
  • [ ] ALLOWED_HOSTS et CSRF_TRUSTED_ORIGINS corrects
  • [ ] HTTPS actif (Caddy ou Nginx+Certbot)
  • [ ] Sauvegardes DB & médias automatisées
  • [ ] python manage.py check --deploy OK
  • [ ] Secrets & mots de passe solides
  • [ ] Seuls les ports 80/443 exposés (reverse proxy)

Commandes utiles (mémo)

# Shell Django
docker compose exec web python manage.py shell

# Logs
docker compose logs -f web
docker compose logs -f nginx

# Celery en cours
docker compose exec worker celery -A kart inspect active

# Nettoyer (⚠️ supprime les volumes)
docker compose down -v

Adaptez ce guide à votre contexte (réglages Django, variables métiers, services annexes). Bon déploiement !