Installer KART avec Docker (from scratch)
Sommaire
- Prérequis
- Arborescence du dépôt
- Variables d’environnement
- Fichiers clés
- Dockerfile
- Entrypoint
- docker-compose
- Nginx (reverse proxy)
- Mise en route
- HTTPS (optionnel)
- Mises à jour / déploiements
- Sauvegardes
- Paramètres Django à vérifier
- Dépannage
- Checklist Prod
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
-
Préparer l’environnement
cp .env.example .env # Éditez .env (ALLOWED_HOSTS, SERVER_NAME, mots de passe…) -
Construire et démarrer
docker compose build docker compose up -d db redis docker compose up -d web -
Créer un superuser
docker compose exec web python manage.py createsuperuser -
(Optionnel) Celery & Nginx
docker compose up -d worker beat docker compose up -d nginx -
Appli :
http://<IP_VPS>(ouhttp://localhosten local) - 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_passpointe-t-il versweb:8000?- Logs :
docker compose logs -f nginx web
Statiques en 404
collectstaticexé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
dbhealthy.
CSRF en HTTPS
- Ajouter le domaine à
ALLOWED_HOSTS CSRF_TRUSTED_ORIGINSdoit inclure le schéma :https://example.com
Celery ne consomme pas
CELERY_BROKER_URL/CELERY_RESULT_BACKENDpointent surredis- Logs :
docker compose logs -f worker beat
Checklist Prod
- [ ]
DJANGO_DEBUG=0 - [ ]
ALLOWED_HOSTSetCSRF_TRUSTED_ORIGINScorrects - [ ] HTTPS actif (Caddy ou Nginx+Certbot)
- [ ] Sauvegardes DB & médias automatisées
- [ ]
python manage.py check --deployOK - [ ] 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 !