Le setup qui sent le terminal, les dotfiles bien rangés et la vélocité clavier.
Objectif simple : ouvrir un IDE cloud et obtenir un poste de dev Django qui claque — shell nerveux, prompts qui parlent, linters qui mordent, hot-reload propre, logs lisibles, raccourcis partout. Zéro bricolage manuel. Tout se déclare via conteneur + dotfiles + scripts idempotents.
Un environnement Django en ligne peut être austère ou plaisant. Version “geek”, il devient rapide, verbeux quand il faut, silencieux quand il faut, et surtout prédictible. Copiez-collez ces fichiers, poussez dans votre repo, ouvrez le workspace : votre équipe a désormais un cockpit clavier-first pour shipper sans yak-shaving.
Dockerfile (dev)
FROM mcr.microsoft.com/devcontainers/python:3.12
# Outils CLI "geek" (rapides & lisibles)
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
git ripgrep fd-find fzf bat eza direnv tmux \
&& ln -s /usr/bin/fdfind /usr/local/bin/fd \
&& ln -s /usr/bin/batcat /usr/local/bin/bat \
&& rm -rf /var/lib/apt/lists/*
# Python tooling (ultra-rapide) : uv + ruff + mypy
RUN pipx install uv && pipx ensurepath
ENV PATH="/root/.local/bin:${PATH}"
# Node minimal (pour Pyright ou front léger)
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt-get install -y nodejs
# Starship prompt
RUN curl -fsSL https://starship.rs/install.sh | sh -s -- -y
.devcontainer/devcontainer.json
{
"name": "django-geek-dev",
"build": { "dockerfile": "Dockerfile" },
"features": {},
"settings": {
"terminal.integrated.defaultProfile.linux": "bash",
"editor.rulers": [100],
"python.analysis.typeCheckingMode": "basic",
"files.trimTrailingWhitespace": true
},
"postCreateCommand": "just bootstrap",
"forwardPorts": [8000],
"remoteEnv": {
"DJANGO_SETTINGS_MODULE": "config.settings.dev"
}
}
~/.bashrc (extrait)
eval "$(direnv hook bash)"
export EDITOR="nvim"
export PIP_DISABLE_PIP_VERSION_CHECK=1
export PYTHONDONTWRITEBYTECODE=1
eval "$(starship init bash)"
alias ll='eza -lh --git --icons --group-directories-first'
alias cat='bat --paging=never'
alias g='git'
~/.config/starship.toml (prompt minimal lisible)
add_newline = false
[directory] truncation_length = 3
[python] format = "via [ $virtualenv]($style) "
[git_branch] symbol = " "
[cmd_duration] min_time = 500
~/.tmux.conf (multiplexage sobre)
setw -g mode-keys vi
set -g mouse on
bind r source-file ~/.tmux.conf \; display "reloaded"
project/
├─ .devcontainer/
├─ .env.example
├─ justfile
├─ pyproject.toml
├─ manage.py
├─ src/
│ ├─ config/
│ │ ├─ asgi.py
│ │ ├─ wsgi.py
│ │ └─ settings/{base.py,dev.py,prod.py}
│ ├─ apps/core/
│ ├─ templates/
│ └─ static/
└─ tests/
uv + ruff + pytestpyproject.toml (extrait)
[project]
name = "django-geek"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
"django>=5.0",
"dj-database-url>=2.2",
"python-dotenv>=1.0",
"django-extensions>=3.2",
] [project.optional-dependencies]
dev = [« pytest », « pytest-django », « ruff », « black », « mypy », « ipython »]
[tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = « config.settings.dev » pythonpath = [« src »]
Installation express
uv pip install -e ".[dev]"
src/config/settings/base.py
from pathlib import Path
import os, dj_database_url
BASE_DIR = Path(__file__).resolve().parents[2]
ENV = os.getenv("ENV", "dev")
def env(key, default=None):
v = os.getenv(key, default)
return None if v is None else v
SECRET_KEY = env("DJANGO_SECRET_KEY", "change-me")
DEBUG = env("DJANGO_DEBUG", "0") == "1"
ALLOWED_HOSTS = env("DJANGO_ALLOWED_HOSTS", "").split(",") if env("DJANGO_ALLOWED_HOSTS") else []
CSRF_TRUSTED_ORIGINS = env("DJANGO_CSRF_TRUSTED_ORIGINS","").split(",") if env("DJANGO_CSRF_TRUSTED_ORIGINS") else []
LANGUAGE_CODE = env("DJANGO_LANGUAGE_CODE", "fr-fr")
TIME_ZONE = env("DJANGO_TIME_ZONE", "Europe/Paris")
USE_I18N = True; USE_TZ = True
INSTALLED_APPS = [
"django.contrib.admin","django.contrib.auth","django.contrib.contenttypes",
"django.contrib.sessions","django.contrib.messages","django.contrib.staticfiles",
"django_extensions","apps.core",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "config.urls"
WSGI_APPLICATION = "config.wsgi.application"
ASGI_APPLICATION = "config.asgi.application"
DATABASES = {"default": dj_database_url.parse(env("DATABASE_URL","sqlite:///db.sqlite3"))}
STATIC_URL = "/static/"; STATIC_ROOT = BASE_DIR / "staticfiles"
MEDIA_URL = "/media/"; MEDIA_ROOT = BASE_DIR / "media"
LOGGING = {
"version": 1, "disable_existing_loggers": False,
"handlers": {"console": {"class": "logging.StreamHandler"}},
"root": {"handlers": ["console"], "level": env("DJANGO_LOG_LEVEL","INFO")},
}
dev.py et prod.py
# dev.py
from .base import *
DEBUG = True
INTERNAL_IPS = ["127.0.0.1"]
# prod.py
from .base import *
SECURE_SSL_REDIRECT = os.getenv("DJANGO_SSL_REDIRECT","1") == "1"
SESSION_COOKIE_SECURE = True; CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = int(os.getenv("DJANGO_HSTS_SECONDS","0"))
.env.example
DJANGO_SECRET_KEY=changeme
DJANGO_DEBUG=1
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1
DJANGO_CSRF_TRUSTED_ORIGINS=http://localhost:8000
DJANGO_LOG_LEVEL=INFO
DJANGO_LANGUAGE_CODE=fr-fr
DJANGO_TIME_ZONE=Europe/Paris
DATABASE_URL=sqlite:///db.sqlite3
DJANGO_SSL_REDIRECT=0
DJANGO_HSTS_SECONDS=0
direnv pour l’auto-load.envrc
dotenv
export DJANGO_SETTINGS_MODULE=config.settings.dev
Puis :
direnv allow
just au lieu de retenir 12 commandesjustfile
set shell := ["bash", "-cu"]
bootstrap:
uv pip install -e ".[dev]"
pre-commit install || true
run:
python manage.py migrate
python manage.py runserver 0.0.0.0:8000
shell:
python manage.py shell_plus
lint:
ruff check .
black --check .
mypy src
fix:
ruff check . --fix
black .
test:
pytest -q
collect:
python manage.py collectstatic --noinput
Exécution :
just bootstrap
just run
• Recherche ultra-rapide : rg "pattern" -n src/
• Fuzzy-open un fichier : fzf puis enter
• Git lisible :
git config --global core.pager "delta"
git log --graph --oneline --decorate --all
• Django shell++ : just shell (via django-extensions)
• Hot-reload fiable : runserver 0.0.0.0:8000 + volumes montés (devcontainer)
• Multiplexage : tmux new -s dj && tmux split-window -h && tmux split-window -v
Panneau gauche : serveur. Haut droit : tests. Bas droit : logs.
Astuce logging de dev : niveaux lisibles, timestamps courts.
# dans base.py, remplacez "handlers" par:
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "simple"
}},
"formatters": {
"simple": { "format": "[%(levelname).1s %(asctime)s] %(name)s: %(message)s",
"datefmt": "%H:%M:%S" }
}
Résultat : [I 14:22:10] django.server: "GET /admin/..." 200
docker-compose.yml (optionnel)
services:
db:
image: postgres:16
environment:
POSTGRES_USER: django
POSTGRES_PASSWORD: django
POSTGRES_DB: django
ports: ["5432:5432"]
redis:
image: redis:7
ports: ["6379:6379"]
Env : DATABASE_URL=postgres://django:django@localhost:5432/django
Bonus Celery/Beat en split-process via Procfile si besoin.
pre-commit (extrait .pre-commit-config.yaml)
repos:
- repo: https://github.com/psf/black
rev: 24.8.0
hooks: [{id: black}]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.9
hooks: [{id: ruff}]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: end-of-file-fixer
- id: detect-private-key
Une fois installé : chaque commit est “peigné” automatiquement.
Ouvrir le workspace → just run → coder → just test → commit/push.
Aucun copier-coller de commandes ésotériques, aucune divergence locale. L’IDE cloud, le conteneur et les dotfiles font le gros du travail.
• python manage.py runserver_plus (Werkzeug debugger — via django-extensions).
• ipdb/breakpoint() pour pause granulaire.
• pytest -k nom -q exécute un seul test, plus vite que tout.
• Profilage express :
from django_extensions.management.commands.runserver_plus import Command
# ou utilisez --print-sql / django-silk en local
But. Transformer votre setup Django “en ligne” en un pipeline CI/CD prêt pour la préprod et la prod : build Docker multi-stage, statiques via WhiteNoise/CDN, médias sur stockage objet, migrations automatiques, workers Celery, healthchecks et monitoring.
gunicorn config.wsgi:applicationcelery -A config worker -l infocelery -A config beat -l info# syntax=docker/dockerfile:1
FROM python:3.12-slim AS build
WORKDIR /app
RUN apt-get update && apt-get install -y build-essential libpq-dev && rm -rf /var/lib/apt/lists/*
COPY pyproject.toml ./
RUN pip install --upgrade pip && pip install --no-cache-dir "pip-tools"
# Si vous utilisez requirements.txt: COPY requirements.txt . && pip install -r requirements.txt
FROM python:3.12-slim AS runtime
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
WORKDIR /app
RUN useradd -m django && apt-get update && apt-get install -y libpq5 && rm -rf /var/lib/apt/lists/*
COPY --from=build /usr/local /usr/local
COPY ./src ./src
COPY manage.py gunicorn.conf.py ./
ENV DJANGO_SETTINGS_MODULE=config.settings.prod
USER django
CMD ["gunicorn","-c","gunicorn.conf.py","config.wsgi:application"]
gunicorn.conf.py (prod)
bind = "0.0.0.0:8000"
workers = 3
threads = 2
timeout = 60
graceful_timeout = 30
accesslog = "-" # stdout
errorlog = "-" # stderr
settings/prod.py (extraits)
from .base import *
INSTALLED_APPS = ["whitenoise.runserver_nostatic", *INSTALLED_APPS]
MIDDLEWARE.insert(1, "whitenoise.middleware.WhiteNoiseMiddleware")
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
SECURE_SSL_REDIRECT = os.getenv("DJANGO_SSL_REDIRECT","1") == "1"
SESSION_COOKIE_SECURE = True; CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = int(os.getenv("DJANGO_HSTS_SECONDS","31536000"))
Build/collectstatic (CI avant image finale)
python manage.py collectstatic --noinput
# settings/prod.py (suite)
INSTALLED_APPS += ["storages"]
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID")
AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY")
AWS_STORAGE_BUCKET_NAME = os.getenv("AWS_STORAGE_BUCKET_NAME")
AWS_S3_REGION_NAME = os.getenv("AWS_S3_REGION_NAME", "eu-west-1")
AWS_S3_OBJECT_PARAMETERS = {"CacheControl": "max-age=31536000, public"}
Env requis
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_STORAGE_BUCKET_NAME, AWS_S3_REGION_NAME
Une vue “prête” pour l’orchestrateur :
# src/apps/core/views.py
from django.http import JsonResponse
def health(request): return JsonResponse({"status":"ok"})
def ready(request): return JsonResponse({"db":"ok","cache":"ok"})
# src/config/urls.py
from django.urls import path
from apps.core.views import health, ready
urlpatterns = [path("health/", health), path("ready/", ready)]
src/config/celery.py
import os
from celery import Celery
os.environ.setdefault("DJANGO_SETTINGS_MODULE","config.settings.prod")
app = Celery("config")
app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks()
Env (ex.)
CELERY_BROKER_URL=redis://redis:6379/0
CELERY_RESULT_BACKEND=redis://redis:6379/1
.github/workflows/ci.yml
name: ci
on:
push: { branches: [main] }
pull_request: {}
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: '3.12' }
- name: Install deps
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Lint & Tests
run: |
ruff check .
black --check .
pytest -q
.github/workflows/cd.yml (build & push image + déploiement via SSH)
name: cd
on:
push: { branches: [main] }
jobs:
build-and-deploy:
runs-on: ubuntu-latest
permissions: { contents: read, packages: write }
steps:
- uses: actions/checkout@v4
- name: Login GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build & Push
uses: docker/build-push-action@v6
with:
push: true
tags: ghcr.io/${{ github.repository }}:latest
- name: Deploy (SSH)
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_KEY }}
script: |
docker pull ghcr.io/${{ github.repository }}:latest
docker compose -f /srv/app/compose.prod.yml up -d --force-recreate
docker compose -f /srv/app/compose.prod.yml exec -T web python manage.py migrate
compose.prod.yml (web + worker + beat)services:
web:
image: ghcr.io/owner/repo:latest
env_file: /srv/app/.env
ports: ["80:8000"]
depends_on: [db, redis]
restart: always
worker:
image: ghcr.io/owner/repo:latest
command: celery -A config worker -l info
env_file: /srv/app/.env
depends_on: [redis]
restart: always
beat:
image: ghcr.io/owner/repo:latest
command: celery -A config beat -l info
env_file: /srv/app/.env
depends_on: [redis]
restart: always
db:
image: postgres:16
volumes: ["pgdata:/var/lib/postgresql/data"]
environment:
POSTGRES_DB: django
POSTGRES_USER: django
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
redis:
image: redis:7
volumes:
pgdata:
DJANGO_SECRET_KEY, DJANGO_ALLOWED_HOSTS, DJANGO_CSRF_TRUSTED_ORIGINS, DJANGO_LOG_LEVELDATABASE_URL, CELERY_BROKER_URL, CELERY_RESULT_BACKENDDJANGO_SSL_REDIRECT=1, DJANGO_HSTS_SECONDS=31536000DJANGO_LANGUAGE_CODE, DJANGO_TIME_ZONE# settings/prod.py
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
SENTRY_DSN = os.getenv("SENTRY_DSN")
if SENTRY_DSN:
sentry_sdk.init(dsn=SENTRY_DSN, integrations=[DjangoIntegration()], traces_sample_rate=0.2)
compose up -dmanage.py migrate (étape dédiée)/health/ et /ready/ → 200composeCSRF_TRUSTED_ORIGINSALLOWED_HOSTScollectstatic oublié ou mauvais STATICFILES_STORAGEDEFAULT_FILE_STORAGECELERY_BROKER_URL incorrect / Redis non joignabletimeout de Gunicorn ou index DBQuand on finance une voiture, tout le monde voit à peu près de quoi il…
On connaît tous ce moment : on tombe sur une offre de leasing “à partir…
Dans l’industrie, parler de maintenance sans préciser le niveau d’intervention revient souvent à créer de…
La Maintenance 1er Niveau - maintenance de niveau 1 - représente la première barrière contre…
Un outil simple pour mesurer la compréhension… et révéler les écarts invisibles Dans beaucoup d’organisations,…
Le levier discret qui transforme l’image, la cohésion et la performance d’une organisation Dans beaucoup…
This website uses cookies.