├── .dockerignore ├── .gitignore ├── .vscode └── launch.json ├── Dockerfile ├── README-PT.md ├── README.md ├── conf ├── nginx.conf └── run_django.sh ├── django_demo ├── django_demo │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── tasks.py │ ├── urls.py │ ├── views.py │ └── wsgi.py ├── manage.py ├── requirements-dev.txt └── requirements.txt ├── docker-compose.qa.yml └── docker-compose.yml /.dockerignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | __pycache__/ 3 | .*/ 4 | django_demo/db.sqlite3 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | __pycache__/ 3 | .mypy** 4 | db.sqlite3 5 | .vscode/* 6 | !.vscode/launch.json -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Python: Current File", 6 | "type": "python", 7 | "request": "launch", 8 | "program": "${file}", 9 | }, 10 | { 11 | "name": "Python: Debug Django", 12 | "type": "python", 13 | "request": "launch", 14 | "program": "${workspaceFolder}/django_demo/manage.py", 15 | "args": [ 16 | "runserver", 17 | "--nothreading" 18 | ], 19 | "subProcess": true, 20 | }, 21 | { 22 | "name": "Python: Debug Django attach Docker", 23 | "type": "python", 24 | "request": "attach", 25 | "localRoot": "${workspaceFolder}/django_demo", 26 | "remoteRoot": "/app", 27 | "host": "127.0.0.1", 28 | "port": 5678, 29 | }, 30 | ] 31 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | ### How to build docker images: 4 | # DOCKER_BUILDKIT=1 docker build -t django-docker-demo:latest . 5 | # DOCKER_BUILDKIT=1 docker build -t django-docker-demo:dev --target=dev . 6 | # DOCKER_BUILDKIT=1 docker build -t django-docker-demo:qa --target=qa . 7 | # DOCKER_BUILDKIT=1 docker build -t django-docker-demo:webserver --target=webserver . 8 | 9 | ################################################################################## 10 | # basepy - base python packages 11 | ################################################################################## 12 | FROM python:3.7-alpine as basepy 13 | 14 | RUN adduser -D user \ 15 | && mkdir /app \ 16 | && chown user: /app 17 | WORKDIR /app 18 | ENV HOME /app 19 | ENV PYTHONUNBUFFERED=1 20 | 21 | RUN --mount=type=cache,id=apk,sharing=locked,target=/var/cache/apk \ 22 | apk add --update \ 23 | bash \ 24 | build-base \ 25 | libevent \ 26 | mariadb-dev \ 27 | python-dev 28 | 29 | COPY --chown=user django_demo/requirements.txt requirements.txt 30 | RUN --mount=type=cache,id=pip,target=/app/.cache/pip \ 31 | pip install -r requirements.txt 32 | 33 | 34 | ################################################################################## 35 | # base - base python stage with code and base packages 36 | ################################################################################## 37 | FROM basepy as base 38 | 39 | COPY --chown=user django_demo/ /app/ 40 | COPY --chown=user conf/run_django.sh /run_django.sh 41 | 42 | CMD /run_django.sh 43 | 44 | ################################################################################## 45 | # dev - all development packages installed 46 | ################################################################################## 47 | FROM basepy as dev 48 | 49 | COPY --chown=user django_demo/requirements-dev.txt requirements-dev.txt 50 | RUN --mount=type=cache,id=pip,target=/app/.cache/pip \ 51 | pip install -r requirements-dev.txt 52 | 53 | COPY --from=base --chown=user /app/ /app/ 54 | COPY --from=base --chown=user /run_django.sh /run_django.sh 55 | 56 | USER user 57 | CMD /run_django.sh 58 | 59 | ################################################################################## 60 | # qa - quality stage. Run tests and other quality steps only 61 | ################################################################################## 62 | FROM dev AS qa 63 | RUN black --target-version=py37 --check --diff . 64 | RUN mypy --python-version=3.7 --pretty --show-error-context . 65 | RUN coverage run django_demo/manage.py test 66 | RUN django_demo/manage.py check --deploy --fail-level WARNING 67 | 68 | ################################################################################## 69 | # webserver - static files and proxy 70 | ################################################################################## 71 | FROM dev AS staticfiles 72 | RUN python manage.py collectstatic --noinput 73 | ################################################################################## 74 | FROM nginx:1.17-alpine AS webserver 75 | COPY conf/nginx.conf /etc/nginx/conf.d/default.conf 76 | COPY --from=staticfiles /app/static /staticfiles/static 77 | 78 | ################################################################################## 79 | # final - official image, for running your application 80 | ################################################################################## 81 | FROM base 82 | USER user 83 | -------------------------------------------------------------------------------- /README-PT.md: -------------------------------------------------------------------------------- 1 | # Este é um tutorial em como correr Django em Docker 2 | 3 | ### Índice 4 | 5 | 1. Iniciar um projeto Django (do zero) 6 | 1. Configurar Docker 7 | 1. Testar Django (com Docker) 8 | 1. Desenvolver com *Docker volumes* 9 | 1. Docker-compose - uma ferramenta simples para gestão de múltiplos *containers* 10 | 1. Depurar (debug) uma aplicação Django 11 | 1. Melhorar um `Dockerfile` e outras coisas... 12 | 1. Testar tudo junto 13 | 1. Notas finais 14 | 15 | ### Inclui: 16 | 17 | - django 18 | - celery 19 | - docker 20 | - boas práticas para desenvolvimento 21 | - boas práticas num Dockerfile 22 | - volumes para um desenvolvimento mais rápido 23 | - depuração (debug) 24 | - algumas boas práticas em produção 25 | - não correr como root 26 | - usar gunicorn e servir o conteúdo estático 27 | - imagens de docker pequenas 28 | - variáveis de ambiente 29 | 30 | 31 | ### Notas introdutórias 32 | 33 | - Este tutorial acompanha esta apresentação: 34 | - http://tcarreira.github.io/presentations/django-docker/pt.html 35 | - ([também em inglês](http://tcarreira.github.io/presentations/django-docker)) 36 | 37 | - Boas práticas em desenvolvimento 38 | 39 | O que é necessário para desenvolver código? (uma lista não exaustiva) 40 | 41 | | ferramenta | para que serve | exemplo | 42 | | --- | --- | --- | 43 | | um bom IDE | auto-completion, debugging | vscode | 44 | | bons plugins no IDE | específico para a framework (django) | `ms-python.python`,
`batisteo.vscode-django` | 45 | | linter | é necessário feedback em tempo real sobre o que está errado | mypy | 46 | | formatador | é fantástico para partilha de código | black | 47 | | testes unitários | ninguém devia testar o seu código. Deixem os computadores fazê-lo | pytest | 48 | | código-execução | mínimo tempo desde a escrita do código até que ele é executado | manage.py runserver | 49 | 50 | 51 | 52 | # Workshop 53 | 54 | 1. Iniciar um projeto Django (do zero) 55 | - Configurar um VirtualEnv e instalar Django 56 | ``` 57 | virtualenv -p $(which python3) venv 58 | . ./venv/bin/activate 59 | pip install "Django>=3.0.3,<4" 60 | ``` 61 | - Criar um projeto Django e configurações iniciais 62 | ``` 63 | django-admin startproject django_demo 64 | python django_demo/manage.py makemigrations 65 | python django_demo/manage.py migrate 66 | python django_demo/manage.py createsuperuser --username admin --email "" 67 | ``` 68 | Podemos correr `python django_demo/manage.py runserver 0.0.0.0:8000` e abrir um navegador em http://127.0.0.1:8000 69 | 70 | 1. Configurar Docker 71 | 72 | - Instalar o Docker 73 | 74 | - NÃO instalar com apt-get: bastante desatualizado (quase inútil) 75 | - Windows - Docker Desktop 76 | - https://docs.docker.com/docker-for-windows/install/ 77 | - Mac - Docker Desktop 78 | - https://docs.docker.com/docker-for-mac/install/ 79 | - Linux 80 | - Super fácil: `wget -qO- https://get.docker.com | sh` 81 | - Outra forma: https://docs.docker.com/install/ 82 | 83 | - Criar um primeiro rascunho de um Dockerfile, para podermos encontrar o que está errado 84 | ```Dockerfile 85 | FROM python 86 | COPY django_demo/ /app/ 87 | RUN pip install --no-cache-dir "Django>=3.0.3,<4" 88 | ENV PYTHONUNBUFFERED=1 89 | CMD python /app/manage.py runserver 0.0.0.0:8000 90 | ``` 91 | e testar 92 | ``` 93 | docker build -t django-docker-demo . 94 | docker run -it --rm -p8000:8000 django-docker-demo 95 | ``` 96 | 97 | primeiro build: 2:18
98 | build após pequena alteração: 0:22 99 | 100 | problemas: 101 | - COPY dos ficheiros antes do pip install 102 | - não usar tags das imagens de docker (usar alpine se possível) 103 | - atenção ao contexto (.dockerignore) 104 | 105 | - Identificar alguns problemas no Dockerfile 106 | ```Dockerfile 107 | FROM python:3.7-alpine 108 | RUN pip install --no-cache-dir "Django>=3.0.3,<4" 109 | COPY django_demo/ /app/ 110 | ENV PYTHONUNBUFFERED=1 111 | CMD python /app/manage.py runserver 0.0.0.0:8000 112 | ``` 113 | e `.dockerignore` 114 | ``` 115 | venv/ 116 | __pycache__/ 117 | db.sqlite3 118 | ``` 119 | De `Sending build context to Docker daemon 42.42MB` passamos a `...156.7kB` 120 | 121 | primeiro build: 1:37
122 | build após pequena alteração: 0:03 123 | 124 | 1. Testar Django (com Docker) 125 | 126 | Vamos criar o nosso próprio conteúdo em Django 127 | 128 | criar `django_demo/django_demo/views.py` 129 | ```python 130 | import os 131 | from django.http import HttpResponse 132 | 133 | def hello_world(request): 134 | output_string = "

Hello World from {}".format(os.environ.get("HOSTNAME", "no_host")) 135 | return HttpResponse(output_string) 136 | ``` 137 | alterar `django_demo/django_demo/urls.py` 138 | ```python 139 | from django.contrib import admin 140 | from django.urls import path 141 | from . import views 142 | 143 | urlpatterns = [ 144 | path('admin/', admin.site.urls), 145 | path('', views.hello_world), 146 | ] 147 | ``` 148 | 149 | 1. Desenvolver com *Docker volumes* 150 | 151 | Apesar da criação de uma imagem de docker ser rápida, não queremos ter de recriar+reiniciar a aplicação cada vez que há alguma alteração 152 | 153 | adicionar um volume no docker run 154 | ``` 155 | docker run -it --rm -p8000:8000 -v "$(pwd)/django_demo/:/app/" django-docker-demo 156 | ``` 157 | 158 | agora, cada vez que algum ficheiro for alterado, o próprio django irá reiniciar. Muito útil para desenvolvimento. 159 | 160 | 1. Docker-compose - uma ferramenta simples para gestão de múltiplos *containers* 161 | 162 | Vamos adicionar algumas dependências: Celery + Redis (**This is a major step**) 163 | 164 | o Celery depende de um broker, e vamos usar Redis para ser mais simples 165 | 166 | - Instalar Celery e cliente de Redis 167 | 168 | (como estamos a adicionar mais dependências, vamos manter um ficheiro à parte com as mesmas `django_demo/requirements.txt`) 169 | ```Dockerfile 170 | Django>=3.0.3,<4 171 | Celery>=4.3.0,<4.4 172 | redis>=3.3<3.4 173 | ``` 174 | e atualizar `Dockerfile` (assim não precisamos de voltar a atualizar quando houver novas dependências) 175 | ```Dockerfile 176 | FROM python:3.7-alpine 177 | 178 | COPY django_demo/requirements.txt /app/requirements.txt 179 | RUN pip install --no-cache-dir -r /app/requirements.txt 180 | COPY django_demo/ /app/ 181 | 182 | ENV PYTHONUNBUFFERED=1 183 | WORKDIR /app 184 | 185 | CMD python /app/manage.py runserver 0.0.0.0:8000 186 | ``` 187 | - Criar um `docker-compose.yml` para ter os 3 serviços a correr: django + redis + celery_worker 188 | ```yaml 189 | version: "3.4" 190 | services: 191 | django: 192 | image: django-docker-demo 193 | ports: 194 | - 8000:8000 195 | volumes: 196 | - ./django_demo/:/app/ 197 | celery-worker: 198 | image: django-docker-demo 199 | volumes: 200 | - ./django_demo/:/app/ 201 | command: "celery -A django_demo.tasks worker --loglevel=info" 202 | redis: 203 | image: redis:5.0-alpine 204 | ``` 205 | - Atualizar o código python com um teste 206 | 207 | criar `django_demo/django_demo/tasks.py` 208 | ```python 209 | from celery import Celery 210 | import os 211 | app = Celery('tasks', broker='redis://redis:6379', backend='redis://redis:6379') 212 | 213 | @app.task 214 | def hello(caller_host): 215 | return "

Hi {}! This is {}.

".format(caller_host, os.environ.get("HOSTNAME", 'celery_worker_hostname')) 216 | ``` 217 | adicioanr ao `django_demo/django_demo/views.py` 218 | ```python 219 | from . import tasks 220 | def test_task(request): 221 | task = tasks.hello.delay(os.environ.get("HOSTNAME", "no_host")) 222 | output_string = task.get(timeout=5) 223 | return HttpResponse(output_string) 224 | ``` 225 | atualizar `django_demo/django_demo/urls.py` 226 | ```python 227 | urlpatterns = [ 228 | path('admin/', admin.site.urls), 229 | path('', views.hello_world), 230 | path('task', views.test_task), 231 | ] 232 | ``` 233 | - Finalmente podemos testar esta configuração 234 | 235 | - build docker image: `docker build -t django-docker-demo .` 236 | - run `docker-compose up` (you can see the logs and terminate with ctrl+c. to run in background, add `-d`) 237 | - open http://127.0.0.1:8000/task 238 | 239 | 240 | Agora que as coisas estão um pouco mais confusas, vamos piorar. 241 | 242 | 243 |

244 | *lembrando algumas boas práticas para desenvolvimento* 245 | 246 | 1. Depurar (debug) uma aplicação Django 247 | 248 | exemplo com vscode, que usa ptvsd para depuração, `.vscode/launch.json` 249 | ```json 250 | { 251 | "version": "0.2.0", 252 | "configurations": [ 253 | { 254 | "name": "Python: Current File", 255 | "type": "python", 256 | "request": "launch", 257 | "program": "${file}", 258 | }, 259 | { 260 | "name": "Python: Debug Django", 261 | "type": "python", 262 | "request": "launch", 263 | "program": "${workspaceFolder}/django_demo/manage.py", 264 | "args": [ 265 | "runserver", 266 | "--nothreading" 267 | ], 268 | "subProcess": true, 269 | } 270 | ] 271 | } 272 | ``` 273 | 274 | O primeiro é suficiente para scripts em python, mas não é muito prático em Django 275 | O segundo é muito bom, mas corre localmente (sem docker) 276 | 277 | ou... 278 | 279 | - Podemos correr o depurador diretamente com docker 280 | - criar um `django_demo/requirements-dev.txt` com as dependências para desenvolvimento (resultado do `pip freeze`) (nota: este exemplo tem demasiados pacotes) 281 | ``` 282 | amqp==2.5.2 283 | appdirs==1.4.3 284 | asgiref==3.2.3 285 | astroid==2.3.3 286 | attrs==19.3.0 287 | billiard==3.6.1.0 288 | black==19.10b0 289 | celery==4.3.0 290 | Click==7.0 291 | Django==3.0.3 292 | importlib-metadata==1.2.0 293 | isort==4.3.21 294 | kombu==4.6.7 295 | lazy-object-proxy==1.4.3 296 | mccabe==0.6.1 297 | more-itertools==8.0.2 298 | mypy==0.750 299 | mypy-extensions==0.4.3 300 | pathspec==0.6.0 301 | ptvsd==4.3.2 302 | pylint==2.4.4 303 | pytz==2019.3 304 | redis==3.3.11 305 | regex==2019.11.1 306 | six==1.13.0 307 | sqlparse==0.3.0 308 | toml==0.10.0 309 | typed-ast==1.4.0 310 | typing-extensions==3.7.4.1 311 | vine==1.3.0 312 | wrapt==1.11.2 313 | zipp==0.6.0 314 | ``` 315 | - alterar o `Dockerfile` para incluir um `requirements-dev.txt` em vez do `requirements.txt` (precisamos das ferramentas de depuração instaladas) e mais algumas dependências 316 | ```dockerfile 317 | FROM python:3.7-alpine 318 | 319 | RUN apk add --update --no-cache \ 320 | bash \ 321 | build-base 322 | 323 | COPY django_demo/requirements-dev.txt /app/requirements.txt 324 | RUN pip install --no-cache-dir -r /app/requirements.txt 325 | COPY django_demo/ /app/ 326 | 327 | ENV PYTHONUNBUFFERED=1 328 | WORKDIR /app 329 | 330 | CMD python /app/manage.py runserver 0.0.0.0:8000 331 | ``` 332 | e voltar a recriar a imagem 333 | ``` 334 | docker build -t django-docker-demo . 335 | ``` 336 | - expôr o porto 5678 no serviço django dentro do `docker-compose.yml` 337 | ```yaml 338 | version: "3.4" 339 | services: 340 | django: 341 | image: django-docker-demo:latest 342 | ports: 343 | - "8000:8000" 344 | - "5678:5678" 345 | volumes: 346 | - "./django_demo/:/app/" 347 | celery-worker: 348 | image: django-docker-demo:latest 349 | volumes: 350 | - "./django_demo/:/app/" 351 | command: "celery -A django_demo.tasks worker --loglevel=info" 352 | redis: 353 | image: redis:5.0-alpine 354 | ``` 355 | - alterar `django_demo/manage.py` (adicionar dentro do *main* antes de `execute_from_command_line(sys.argv)`) 356 | ```python 357 | from django.conf import settings 358 | if settings.DEBUG: 359 | if ( # as reload relauches itself, workaround for it 360 | "--noreload" not in sys.argv 361 | and os.environ.get("PTVSD_RELOAD", "no") == "no" 362 | ): 363 | os.environ["PTVSD_RELOAD"] = "yes" 364 | else: 365 | import ptvsd 366 | ptvsd.enable_attach() 367 | ``` 368 | - adicionar um depurador remoto dentro do IDE. No caso do vscode, adicionar a confuração ao `.vscode/launch.json` 369 | ```json 370 | { 371 | "name": "Python: Debug Django attach Docker", 372 | "type": "python", 373 | "request": "attach", 374 | "localRoot": "${workspaceFolder}/django_demo", 375 | "remoteRoot": "/app", 376 | "host": "127.0.0.1", 377 | "port": 5678, 378 | }, 379 | ``` 380 | - e testar 381 | ``` 382 | docker-compose up 383 | ``` 384 | Depois adicionar um *breakpoint* dentro da função `django_demo.views.hello_world()` e recarregar o browser. 385 | 386 | 1. Melhorar um *Dockerfile* e outras coisas... 387 | 388 | Talvez seja uma boa ideia fazer apenas `git clone https://github.com/tcarreira/django-docker.git` 389 | 390 | - não correr como root 391 | ```Dockerfile 392 | RUN adduser -D user 393 | USER user 394 | ``` 395 | - remover dependências desnecessárias. Manter imagens diferentes para diferentes casos (usar multi-stage builds) 396 | ```Dockerfile 397 | FROM python:3.7-alpine AS base 398 | ... 399 | FROM base AS dev 400 | ... 401 | FROM base AS final 402 | ... 403 | ``` 404 | - manter os registos (logs) limpos 405 | ```yaml 406 | services: 407 | app: 408 | ... 409 | logging: 410 | options: 411 | max-size: "10m" 412 | max-file: "3" 413 | ``` 414 | ou de forma mais curta: 415 | ```yaml 416 | logging: { options: { max-size: "10m", max-file: "3" } } 417 | ``` 418 | - cache de nível avançado (com Buildkit `DOCKER_BUILDKIT=1`)- https://github.com/moby/buildkit 419 | ```Dockerfile 420 | # syntax=docker/dockerfile:experimental 421 | ... 422 | ENV HOME /app 423 | WORKDIR /app 424 | RUN --mount=type=cache,uid=0,target=/app/.cache/pip,from=base \ 425 | pip install -r requirements.txt 426 | ``` 427 | **note**: `# syntax=docker/dockerfile:experimental` na primeira linha do `Dockerfile` é obrigatório para usar as novas funcionalidades do BUILDKIT 428 | - qualidade como parte da linha de montagem 429 | ```Dockerfile 430 | FROM dev AS qa 431 | RUN black --target-version=py37 --check --diff . 432 | RUN mypy --python-version=3.7 --pretty --show-error-context . 433 | RUN coverage run django_demo/manage.py test 434 | RUN django_demo/manage.py check --deploy --fail-level WARNING 435 | ``` 436 | 437 | - Preparar o Django para produção 438 | - Preparar o Django para produção - https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ (fora do âmbiro deste tutorial) 439 | - usar um servidor web
440 | > em: https://docs.djangoproject.com/en/3.0/ref/django-admin/#runserver
441 | NÂO USE ESTE SERVIDOR EM PRODUÇÃO. Ele não passou pela auditoria de segurança nem por testes de eficiência. 442 | (E é assim que se vai manter. Estamos no negócio de produzir uma framework Web, não de servidores web, 443 | portanto está fora do âmbito do Django melhorar este servidor para aguentar um ambiente de produção.) 444 | 445 | e gerar o conteúdo estático (necessário para o novo servidor web) 446 | ```Dockerfile 447 | FROM dev AS staticfiles 448 | RUN python manage.py collectstatic --noinput 449 | 450 | FROM nginx:1.17-alpine 451 | COPY conf/nginx.conf /etc/nginx/conf.d/default.conf 452 | COPY --from=staticfiles /app/static /staticfiles/static 453 | ``` 454 | e criar o `conf/nginx.conf` correspondente (este é só um exemplo mínimo funcional. Não está preparado para produção) 455 | ``` 456 | server { 457 | listen 80 default_server; 458 | server_name _; 459 | 460 | location /static { 461 | root /staticfiles; 462 | } 463 | 464 | location / { 465 | proxy_pass http://django:8000; 466 | } 467 | } 468 | ``` 469 | 470 | - provavelmente vamos querer usar uma base de dados diferente 471 | 472 | adicionar ao `docker-compose.yml` 473 | ```yaml 474 | db: 475 | image: mariadb:10.4 476 | restart: always 477 | environment: 478 | MYSQL_DATABASE: "django" 479 | MYSQL_USER: "user" 480 | MYSQL_PASSWORD: "pass" 481 | MYSQL_RANDOM_ROOT_PASSWORD: "yes" 482 | volumes: 483 | - django-db:/var/lib/mysql 484 | ``` 485 | e alterar configurações em `django_demo/django_demo/settings.py` 486 | ```python 487 | # If database is defined, overrides the default 488 | if ( 489 | os.environ.get("DJANGO_DB_HOST") 490 | and os.environ.get("DJANGO_DB_DATABASE") 491 | and os.environ.get("DJANGO_DB_USER") 492 | and os.environ.get("DJANGO_DB_PASSWORD") 493 | ): 494 | DATABASES["default"] = { 495 | "ENGINE": "django.db.backends.mysql", 496 | "NAME": os.environ.get("DJANGO_DB_DATABASE", ""), 497 | "USER": os.environ.get("DJANGO_DB_USER", ""), 498 | "PASSWORD": os.environ.get("DJANGO_DB_PASSWORD", ""), 499 | "HOST": os.environ.get("DJANGO_DB_HOST", ""), 500 | "PORT": os.environ.get("DJANGO_DB_PORT", "3306"), 501 | } 502 | ``` 503 | 504 | - criar um entrypoint comum (para que não tenhamos de alterar o dockerfile mais tarde) 505 | ```bash 506 | #!/bin/bash 507 | 508 | # esperar que a base de dados esteja pronta 509 | if [[ ${DJANGO_DB_HOST} != "" ]]; then 510 | while != 1.25 para correr o buildkit diretamente. Caso não esteja disponível, primeiro é necessário criar as imagens e só depois correr `docker-compose up` 602 | 603 | 604 | 1. Notas finais 605 | 606 | - criar um `docker-compose.qa.yml` separado para testes de qualidade com as imagens finais 607 | ```yaml 608 | version: "3.4" 609 | services: 610 | webserver: 611 | image: django-docker-demo:webserver 612 | ports: 613 | - 80:80 614 | logging: { options: { max-size: "10m", max-file: "3" } } 615 | 616 | django: 617 | image: django-docker-demo:latest 618 | environment: 619 | DJANGO_DEBUG: "false" 620 | DJANGO_SECRET_KEY: "AVhqJxkBn5cSS7Zp4jqWAMMAOXRoKfuOHduKVFUo" 621 | DJANGO_DB_HOST: "db" 622 | DJANGO_DB_DATABASE: "djangoqa" 623 | DJANGO_DB_USER: "djangouser" 624 | DJANGO_DB_PASSWORD: "djangouserpassword" 625 | logging: { options: { max-size: "10m", max-file: "3" } } 626 | 627 | celery-worker: 628 | image: django-docker-demo:latest 629 | command: "celery -A django_demo.tasks worker --loglevel=info" 630 | logging: { options: { max-size: "10m", max-file: "3" } } 631 | 632 | redis: 633 | image: redis:5.0-alpine 634 | logging: { options: { max-size: "10m", max-file: "3" } } 635 | 636 | db: 637 | image: mariadb:10.4 638 | restart: always 639 | environment: 640 | MYSQL_DATABASE: "djangoqa" 641 | MYSQL_USER: "djangouser" 642 | MYSQL_PASSWORD: "djangouserpassword" 643 | MYSQL_RANDOM_ROOT_PASSWORD: "yes" 644 | volumes: 645 | - django-db-qa:/var/lib/mysql 646 | logging: { options: { max-size: "10m", max-file: "3" } } 647 | 648 | volumes: 649 | django-db-qa: 650 | ``` 651 | 652 | - criar uma linha de montagem para integração contínua (CI/CD) (este é apenas um exemplo simples) 653 | 654 | - Código + commit + push 655 | - iniciar automaticamente o processo CI/CD 656 | - `DOCKER_BUILDKIT=1 docker build -t django-docker-demo:qa --target=qa .` (esta é a fase de testes) 657 | - `DOCKER_BUILDKIT=1 docker build -t django-docker-demo:dev --target=dev .` + push da imagem de docker dev, internamente (opcional) 658 | - `DOCKER_BUILDKIT=1 docker build -t django-docker-demo:latest --target .` + push da imagem de docker oficial 659 | - atualizar os containers em execução (fora do âmbito deste tutorial) 660 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | (also in PT-PT: https://github.com/tcarreira/django-docker/blob/master/README-PT.md) 2 | 3 | # This is a tutorial on how to run Django with Docker 4 | 5 | ### TOC 6 | 7 | 1. Start a Django project (from scratch) 8 | 1. Setup Docker 9 | 1. Testing Django 10 | 1. Docker volumes for development 11 | 1. Docker-compose - a simple multi-container orchestration tool 12 | 1. Debugging a Django application 13 | 1. Improve `Dockerfile` and other thing... 14 | 1. Test it all together 15 | 1. Last notes 16 | 17 | ### Includes: 18 | 19 | - django 20 | - celery 21 | - docker 22 | - best practices for development 23 | - best practices for Dockerfile 24 | - volumes for fast development 25 | - debugging 26 | - some production best practices 27 | - do not run as root 28 | - use gunicorn and serve static content 29 | - small docker image 30 | - environemnt variables 31 | 32 | 33 | ### Previous notes 34 | 35 | - This tutorial comes along with this presentation: 36 | - http://tcarreira.github.io/presentations/django-docker 37 | - ([also in portuguese - PT](http://tcarreira.github.io/presentations/django-docker/pt.html)) 38 | 39 | - Development best practices 40 | 41 | What do you need to develop? (not an exaustive list) 42 | 43 | | tool | what for | example | 44 | | --- | --- | --- | 45 | | a good IDE | auto-completion, debugging | vscode | 46 | | good IDE plugins | framework specifics (django) | `ms-python.python`,
`batisteo.vscode-django` | 47 | | linter | you need to have a real-time feedback about what is wrong | mypy | 48 | | formatter | it's great for code sharing | black | 49 | | unit tests | you should really not test your code. Make your computer do it | pytest | 50 | | code-to-execution | low latency after writing your code until it gets executed | manage.py runserver | 51 | 52 | 53 | 54 | # Workshop 55 | 56 | 1. Start a Django project (from scratch) 57 | - Setup VirtualEnv and install Django 58 | ``` 59 | virtualenv -p $(which python3) venv 60 | . ./venv/bin/activate 61 | pip install "Django>=3.0.3,<4" 62 | ``` 63 | - Create django project and initial setup 64 | ``` 65 | django-admin startproject django_demo 66 | python django_demo/manage.py makemigrations 67 | python django_demo/manage.py migrate 68 | python django_demo/manage.py createsuperuser --username admin --email "" 69 | ``` 70 | You may test it with `python django_demo/manage.py runserver 0.0.0.0:8000` and open the browser at http://127.0.0.1:8000 71 | 72 | 1. Setup Docker 73 | 74 | - Install Docker 75 | 76 | - Do NOT install from apt-get: very outdated (almost useless) 77 | - Windows - Docker Desktop 78 | - https://docs.docker.com/docker-for-windows/install/ 79 | - Mac - Docker Desktop 80 | - https://docs.docker.com/docker-for-mac/install/ 81 | - Linux 82 | - Brain dead easy way: `wget -qO- https://get.docker.com | sh` 83 | - Other way: https://docs.docker.com/install/ 84 | 85 | - Setup a first draft of a Dockerfile, so we can see what is bad in this draft 86 | ```Dockerfile 87 | FROM python 88 | COPY django_demo/ /app/ 89 | RUN pip install --no-cache-dir "Django>=3.0.3,<4" 90 | ENV PYTHONUNBUFFERED=1 91 | CMD python /app/manage.py runserver 0.0.0.0:8000 92 | ``` 93 | and test it 94 | ``` 95 | docker build -t django-docker-demo . 96 | docker run -it --rm -p8000:8000 django-docker-demo 97 | ``` 98 | 99 | build for the first time: 2:18
100 | build after minor change: 0:22 101 | 102 | problems: 103 | - COPY files before pip install 104 | - not using docker images tags (use alpine if possible) 105 | - beware of context (.dockerignore) 106 | 107 | - Identify some problems in Dockerfile 108 | ```Dockerfile 109 | FROM python:3.7-alpine 110 | RUN pip install --no-cache-dir "Django>=3.0.3,<4" 111 | COPY django_demo/ /app/ 112 | ENV PYTHONUNBUFFERED=1 113 | CMD python /app/manage.py runserver 0.0.0.0:8000 114 | ``` 115 | and `.dockerignore` 116 | ``` 117 | venv/ 118 | __pycache__/ 119 | db.sqlite3 120 | ``` 121 | From `Sending build context to Docker daemon 42.42MB` to `...156.7kB` 122 | 123 | build for the first time: 1:37
124 | build after minor change: 0:03 125 | 126 | 1. Testing Django (with Docker) 127 | 128 | Let's create our own Django content 129 | 130 | create `django_demo/django_demo/views.py` 131 | ```python 132 | import os 133 | from django.http import HttpResponse 134 | 135 | def hello_world(request): 136 | output_string = "

Hello World from {}".format(os.environ.get("HOSTNAME", "no_host")) 137 | return HttpResponse(output_string) 138 | ``` 139 | change `django_demo/django_demo/urls.py` 140 | ```python 141 | from django.contrib import admin 142 | from django.urls import path 143 | from . import views 144 | 145 | urlpatterns = [ 146 | path('admin/', admin.site.urls), 147 | path('', views.hello_world), 148 | ] 149 | ``` 150 | 151 | 1. Docker volumes for development 152 | 153 | Dispite the fast building time, I don't want to rebuild+restart every time I change something 154 | 155 | add volume on docker run 156 | ``` 157 | docker run -it --rm -p8000:8000 -v "$(pwd)/django_demo/:/app/" django-docker-demo 158 | ``` 159 | 160 | now, every time you change some file, django wil reload itself. Very useful for developmemt. 161 | 162 | 1. Docker-compose - a simple multi-container orchestration tool 163 | 164 | Let's add some more dependencies: Celery + Redis (**This is a major step**) 165 | 166 | Celery depends on a message broker, and we are going to use Redis, for simplicity 167 | 168 | - Install Celery and Redis client 169 | 170 | (as we are getting more dependencies, let's keep a `django_demo/requirements.txt`) 171 | ```Dockerfile 172 | Django>=3.0.3,<4 173 | Celery>=4.3.0,<4.4 174 | redis>=3.3<3.4 175 | ``` 176 | and update `Dockerfile` (now we don't need to update this with every dependency change) 177 | ```Dockerfile 178 | FROM python:3.7-alpine 179 | 180 | COPY django_demo/requirements.txt /app/requirements.txt 181 | RUN pip install --no-cache-dir -r /app/requirements.txt 182 | COPY django_demo/ /app/ 183 | 184 | ENV PYTHONUNBUFFERED=1 185 | WORKDIR /app 186 | 187 | CMD python /app/manage.py runserver 0.0.0.0:8000 188 | ``` 189 | - Create a `docker-compose.yml` so we can keep 3 running services: django + redis + celery_worker 190 | ```yaml 191 | version: "3.4" 192 | services: 193 | django: 194 | image: django-docker-demo 195 | ports: 196 | - 8000:8000 197 | volumes: 198 | - ./django_demo/:/app/ 199 | celery-worker: 200 | image: django-docker-demo 201 | volumes: 202 | - ./django_demo/:/app/ 203 | command: "celery -A django_demo.tasks worker --loglevel=info" 204 | redis: 205 | image: redis:5.0-alpine 206 | ``` 207 | - Update python code with a test 208 | 209 | create `django_demo/django_demo/tasks.py` 210 | ```python 211 | from celery import Celery 212 | import os 213 | app = Celery('tasks', broker='redis://redis:6379', backend='redis://redis:6379') 214 | 215 | @app.task 216 | def hello(caller_host): 217 | return "

Hi {}! This is {}.

".format(caller_host, os.environ.get("HOSTNAME", 'celery_worker_hostname')) 218 | ``` 219 | add to `django_demo/django_demo/views.py` 220 | ```python 221 | from . import tasks 222 | def test_task(request): 223 | task = tasks.hello.delay(os.environ.get("HOSTNAME", "no_host")) 224 | output_string = task.get(timeout=5) 225 | return HttpResponse(output_string) 226 | ``` 227 | update `django_demo/django_demo/urls.py` 228 | ```python 229 | urlpatterns = [ 230 | path('admin/', admin.site.urls), 231 | path('', views.hello_world), 232 | path('task', views.test_task), 233 | ] 234 | ``` 235 | - Finally, we can test our setup 236 | 237 | - build docker image: `docker build -t django-docker-demo .` 238 | - run `docker-compose up` (you can see the logs and terminate with ctrl+c. to run in background, add `-d`) 239 | - open http://127.0.0.1:8000/task 240 | 241 | 242 | Now that things got a little confusing, it gets worse. 243 | 244 | 245 |

246 | *do you remeber some development best practices?* 247 | 248 | 1. Debugging a Django application 249 | 250 | example with vscode, which uses ptvsd for debugging, `.vscode/launch.json` 251 | ```json 252 | { 253 | "version": "0.2.0", 254 | "configurations": [ 255 | { 256 | "name": "Python: Current File", 257 | "type": "python", 258 | "request": "launch", 259 | "program": "${file}", 260 | }, 261 | { 262 | "name": "Python: Debug Django", 263 | "type": "python", 264 | "request": "launch", 265 | "program": "${workspaceFolder}/django_demo/manage.py", 266 | "args": [ 267 | "runserver", 268 | "--nothreading" 269 | ], 270 | "subProcess": true, 271 | } 272 | ] 273 | } 274 | ``` 275 | 276 | The first one is good enough for python scripts, but not so nice for django applications. 277 | The second one is very good, but it runs locally (no docker) 278 | 279 | or... 280 | 281 | - You could be running a debugger with docker 282 | - create a `django_demo/requirements-dev.txt` which has every dev package (result from `pip freeze`) (note: this example has too many packages) 283 | ``` 284 | amqp==2.5.2 285 | appdirs==1.4.3 286 | asgiref==3.2.3 287 | astroid==2.3.3 288 | attrs==19.3.0 289 | billiard==3.6.1.0 290 | black==19.10b0 291 | celery==4.3.0 292 | Click==7.0 293 | Django==3.0.3 294 | importlib-metadata==1.2.0 295 | isort==4.3.21 296 | kombu==4.6.7 297 | lazy-object-proxy==1.4.3 298 | mccabe==0.6.1 299 | more-itertools==8.0.2 300 | mypy==0.750 301 | mypy-extensions==0.4.3 302 | pathspec==0.6.0 303 | ptvsd==4.3.2 304 | pylint==2.4.4 305 | pytz==2019.3 306 | redis==3.3.11 307 | regex==2019.11.1 308 | six==1.13.0 309 | sqlparse==0.3.0 310 | toml==0.10.0 311 | typed-ast==1.4.0 312 | typing-extensions==3.7.4.1 313 | vine==1.3.0 314 | wrapt==1.11.2 315 | zipp==0.6.0 316 | ``` 317 | - change the `Dockerfile` in order to include `requirements-dev.txt` instead of `requirements.txt` (we need development tools for debugging) and some other dependencies 318 | ```dockerfile 319 | FROM python:3.7-alpine 320 | 321 | RUN apk add --update --no-cache \ 322 | bash \ 323 | build-base 324 | 325 | COPY django_demo/requirements-dev.txt /app/requirements.txt 326 | RUN pip install --no-cache-dir -r /app/requirements.txt 327 | COPY django_demo/ /app/ 328 | 329 | ENV PYTHONUNBUFFERED=1 330 | WORKDIR /app 331 | 332 | CMD python /app/manage.py runserver 0.0.0.0:8000 333 | ``` 334 | and build it again 335 | ``` 336 | docker build -t django-docker-demo . 337 | ``` 338 | - expose port 5678 on django service inside `docker-compose.yml` 339 | ```yaml 340 | version: "3.4" 341 | services: 342 | django: 343 | image: django-docker-demo:latest 344 | ports: 345 | - "8000:8000" 346 | - "5678:5678" 347 | volumes: 348 | - "./django_demo/:/app/" 349 | celery-worker: 350 | image: django-docker-demo:latest 351 | volumes: 352 | - "./django_demo/:/app/" 353 | command: "celery -A django_demo.tasks worker --loglevel=info" 354 | redis: 355 | image: redis:5.0-alpine 356 | ``` 357 | - modify `django_demo/manage.py` (add it inside main, before `execute_from_command_line(sys.argv)`) 358 | ```python 359 | from django.conf import settings 360 | if settings.DEBUG: 361 | if ( # as reload relauches itself, workaround for it 362 | "--noreload" not in sys.argv 363 | and os.environ.get("PTVSD_RELOAD", "no") == "no" 364 | ): 365 | os.environ["PTVSD_RELOAD"] = "yes" 366 | else: 367 | import ptvsd 368 | ptvsd.enable_attach() 369 | ``` 370 | - add a remote debugger on your IDE. For vscode add a configuration to `.vscode/launch.json` 371 | ```json 372 | { 373 | "name": "Python: Debug Django attach Docker", 374 | "type": "python", 375 | "request": "attach", 376 | "localRoot": "${workspaceFolder}/django_demo", 377 | "remoteRoot": "/app", 378 | "host": "127.0.0.1", 379 | "port": 5678, 380 | }, 381 | ``` 382 | - and test it 383 | ``` 384 | docker-compose up 385 | ``` 386 | After adding a breakpoint inside `django_demo.views.hello_world()` reload your browser. 387 | 388 | 1. Improve `Dockerfile` and other thing... 389 | 390 | You may clone the code with everying with `git clone https://github.com/tcarreira/django-docker.git` 391 | 392 | - do not run as root. 393 | ```Dockerfile 394 | RUN adduser -D user 395 | USER user 396 | ``` 397 | - remove unnecessary dependencies. Keep different images for different uses (use multi-stage builds) 398 | ```Dockerfile 399 | FROM python:3.7-alpine AS base 400 | ... 401 | FROM base AS dev 402 | ... 403 | FROM base AS final 404 | ... 405 | ``` 406 | - clean your logs 407 | ```yaml 408 | services: 409 | app: 410 | ... 411 | logging: 412 | options: 413 | max-size: "10m" 414 | max-file: "3" 415 | ``` 416 | or shorter: 417 | ```yaml 418 | logging: { options: { max-size: "10m", max-file: "3" } } 419 | ``` 420 | - next level caching (with Buildkit `DOCKER_BUILDKIT=1`) - https://github.com/moby/buildkit 421 | ```Dockerfile 422 | # syntax=docker/dockerfile:experimental 423 | ... 424 | ENV HOME /app 425 | WORKDIR /app 426 | RUN --mount=type=cache,uid=0,target=/app/.cache/pip,from=base \ 427 | pip install -r requirements.txt 428 | ``` 429 | **note**: `# syntax=docker/dockerfile:experimental` on the first line of your `Dockerfile` is mandatory for using BUILDKIT new features 430 | - quality as part of the pipeline 431 | ```Dockerfile 432 | FROM dev AS qa 433 | RUN black --target-version=py37 --check --diff . 434 | RUN mypy --python-version=3.7 --pretty --show-error-context . 435 | RUN coverage run django_demo/manage.py test 436 | RUN django_demo/manage.py check --deploy --fail-level WARNING 437 | ``` 438 | 439 | - Prepare Django for production 440 | - Prepare Django for production - https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ (out of the scope for this) 441 | - Use a decent webserver
442 | > from: https://docs.djangoproject.com/en/3.0/ref/django-admin/#runserver
443 | DO NOT USE THIS SERVER IN A PRODUCTION SETTING. It has not gone through security audits or performance tests. 444 | (And that’s how it’s gonna stay. We’re in the business of making Web frameworks, not Web servers, 445 | so improving this server to be able to handle a production environment is outside the scope of Django.) 446 | 447 | and build static files (you will need it for the new webserver) 448 | ```Dockerfile 449 | FROM dev AS staticfiles 450 | RUN python manage.py collectstatic --noinput 451 | 452 | FROM nginx:1.17-alpine 453 | COPY conf/nginx.conf /etc/nginx/conf.d/default.conf 454 | COPY --from=staticfiles /app/static /staticfiles/static 455 | ``` 456 | and create the corresponding `conf/nginx.conf` (just a minimal example. Don't use in production) 457 | ``` 458 | server { 459 | listen 80 default_server; 460 | server_name _; 461 | 462 | location /static { 463 | root /staticfiles; 464 | } 465 | 466 | location / { 467 | proxy_pass http://django:8000; 468 | } 469 | } 470 | ``` 471 | 472 | - you probably want to use a different database 473 | 474 | add to your `docker-compose.yml` 475 | ```yaml 476 | db: 477 | image: mariadb:10.4 478 | restart: always 479 | environment: 480 | MYSQL_DATABASE: "django" 481 | MYSQL_USER: "user" 482 | MYSQL_PASSWORD: "pass" 483 | MYSQL_RANDOM_ROOT_PASSWORD: "yes" 484 | volumes: 485 | - django-db:/var/lib/mysql 486 | ``` 487 | and configure your `django_demo/django_demo/settings.py` 488 | ```python 489 | # If database is defined, overrides the default 490 | if ( 491 | os.environ.get("DJANGO_DB_HOST") 492 | and os.environ.get("DJANGO_DB_DATABASE") 493 | and os.environ.get("DJANGO_DB_USER") 494 | and os.environ.get("DJANGO_DB_PASSWORD") 495 | ): 496 | DATABASES["default"] = { 497 | "ENGINE": "django.db.backends.mysql", 498 | "NAME": os.environ.get("DJANGO_DB_DATABASE", ""), 499 | "USER": os.environ.get("DJANGO_DB_USER", ""), 500 | "PASSWORD": os.environ.get("DJANGO_DB_PASSWORD", ""), 501 | "HOST": os.environ.get("DJANGO_DB_HOST", ""), 502 | "PORT": os.environ.get("DJANGO_DB_PORT", "3306"), 503 | } 504 | ``` 505 | 506 | - build a common entrypoint (so you don't have to change dockerfile later) 507 | ```bash 508 | #!/bin/bash 509 | 510 | # wait until database is ready 511 | if [[ ${DJANGO_DB_HOST} != "" ]]; then 512 | while != 1.25 in order to use builkit directly. If you don't have it, build images first, then run `docker-compose up` 604 | 605 | 606 | 1. Last notes 607 | 608 | - keep separate `docker-compose.qa.yml` for testing/qa with final images 609 | ```yaml 610 | version: "3.4" 611 | services: 612 | webserver: 613 | image: django-docker-demo:webserver 614 | ports: 615 | - 80:80 616 | logging: { options: { max-size: "10m", max-file: "3" } } 617 | 618 | django: 619 | image: django-docker-demo:latest 620 | environment: 621 | DJANGO_DEBUG: "false" 622 | DJANGO_SECRET_KEY: "AVhqJxkBn5cSS7Zp4jqWAMMAOXRoKfuOHduKVFUo" 623 | DJANGO_DB_HOST: "db" 624 | DJANGO_DB_DATABASE: "djangoqa" 625 | DJANGO_DB_USER: "djangouser" 626 | DJANGO_DB_PASSWORD: "djangouserpassword" 627 | logging: { options: { max-size: "10m", max-file: "3" } } 628 | 629 | celery-worker: 630 | image: django-docker-demo:latest 631 | command: "celery -A django_demo.tasks worker --loglevel=info" 632 | logging: { options: { max-size: "10m", max-file: "3" } } 633 | 634 | redis: 635 | image: redis:5.0-alpine 636 | logging: { options: { max-size: "10m", max-file: "3" } } 637 | 638 | db: 639 | image: mariadb:10.4 640 | restart: always 641 | environment: 642 | MYSQL_DATABASE: "djangoqa" 643 | MYSQL_USER: "djangouser" 644 | MYSQL_PASSWORD: "djangouserpassword" 645 | MYSQL_RANDOM_ROOT_PASSWORD: "yes" 646 | volumes: 647 | - django-db-qa:/var/lib/mysql 648 | logging: { options: { max-size: "10m", max-file: "3" } } 649 | 650 | volumes: 651 | django-db-qa: 652 | ``` 653 | 654 | - And build your continuous integration process (this is a simple example) 655 | 656 | - Code + commit + push 657 | - auto-start CI/CD process 658 | - `DOCKER_BUILDKIT=1 docker build -t django-docker-demo:qa --target=qa .` (this is the test stage) 659 | - `DOCKER_BUILDKIT=1 docker build -t django-docker-demo:dev --target=dev .` + push docker image dev internally (optional) 660 | - `DOCKER_BUILDKIT=1 docker build -t django-docker-demo:latest --target .` + push official docker image 661 | - update running containers (outside the scope of this tutorial) 662 | -------------------------------------------------------------------------------- /conf/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default_server; 3 | server_name _; 4 | 5 | location /static { 6 | root /staticfiles; 7 | } 8 | 9 | location / { 10 | proxy_pass http://django:8000; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /conf/run_django.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # wait until database is ready 4 | if [[ ${DJANGO_DB_HOST} != "" ]]; then 5 | while !{output_string}") 18 | -------------------------------------------------------------------------------- /django_demo/django_demo/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for django_demo project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_demo.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /django_demo/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | from django.conf import settings 6 | 7 | 8 | def main(): 9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_demo.settings") 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | 19 | if settings.DEBUG: 20 | if ( # as reload relauches itself, workaround for it 21 | "--noreload" not in sys.argv 22 | and os.environ.get("PTVSD_RELOAD", "no") == "no" 23 | ): 24 | os.environ["PTVSD_RELOAD"] = "yes" 25 | else: 26 | import ptvsd 27 | 28 | ptvsd.enable_attach() 29 | if os.environ.get("DEBUGGER_WAIT_FOR_ATTACH", False): 30 | ptvsd.wait_for_attach() 31 | 32 | execute_from_command_line(sys.argv) 33 | 34 | 35 | if __name__ == "__main__": 36 | main() 37 | -------------------------------------------------------------------------------- /django_demo/requirements-dev.txt: -------------------------------------------------------------------------------- 1 | amqp==2.5.2 2 | appdirs==1.4.3 3 | asgiref==3.2.3 4 | astroid==2.3.3 5 | attrs==19.3.0 6 | billiard==3.6.1.0 7 | black==19.10b0 8 | celery==4.3.0 9 | Click==7.0 10 | coverage==4.5.4 11 | Django==3.1.12 12 | gevent==1.4.0 13 | greenlet==0.4.15 14 | gunicorn==20.0.4 15 | importlib-metadata==1.2.0 16 | isort==4.3.21 17 | kombu==4.6.7 18 | lazy-object-proxy==1.4.3 19 | mccabe==0.6.1 20 | more-itertools==8.0.2 21 | mypy==0.750 22 | mypy-extensions==0.4.3 23 | mysqlclient==1.4 24 | pathspec==0.6.0 25 | ptvsd==4.3.2 26 | pylint==2.4.4 27 | pytz==2019.3 28 | redis==3.3.11 29 | regex==2019.11.1 30 | six==1.13.0 31 | sqlparse==0.3.0 32 | toml==0.10.0 33 | typed-ast==1.4.0 34 | typing-extensions==3.7.4.1 35 | vine==1.3.0 36 | wrapt==1.11.2 37 | zipp==0.6.0 38 | -------------------------------------------------------------------------------- /django_demo/requirements.txt: -------------------------------------------------------------------------------- 1 | Django>=3.0.7,<4 2 | Celery>=4.3.0,<4.4 3 | gevent>=1.4,<1.5 4 | gunicorn>=20.0,<20.1 5 | redis>=3.3<3.4 6 | mysqlclient>=1.4,<1.5 7 | -------------------------------------------------------------------------------- /docker-compose.qa.yml: -------------------------------------------------------------------------------- 1 | version: "3.4" 2 | services: 3 | webserver: 4 | image: django-docker-demo:webserver 5 | ports: 6 | - 80:80 7 | logging: { options: { max-size: "10m", max-file: "3" } } 8 | 9 | django: 10 | image: django-docker-demo:latest 11 | environment: 12 | DJANGO_DEBUG: "false" 13 | DJANGO_SECRET_KEY: "AVhqJxkBn5cSS7Zp4jqWAMMAOXRoKfuOHduKVFUo" 14 | DJANGO_DB_HOST: "db" 15 | DJANGO_DB_DATABASE: "djangoqa" 16 | DJANGO_DB_USER: "djangouser" 17 | DJANGO_DB_PASSWORD: "djangouserpassword" 18 | logging: { options: { max-size: "10m", max-file: "3" } } 19 | 20 | celery-worker: 21 | image: django-docker-demo:latest 22 | command: "celery -A django_demo.tasks worker --loglevel=info" 23 | logging: { options: { max-size: "10m", max-file: "3" } } 24 | 25 | redis: 26 | image: redis:5.0-alpine 27 | logging: { options: { max-size: "10m", max-file: "3" } } 28 | 29 | db: 30 | image: mariadb:10.4 31 | restart: always 32 | environment: 33 | MYSQL_DATABASE: "djangoqa" 34 | MYSQL_USER: "djangouser" 35 | MYSQL_PASSWORD: "djangouserpassword" 36 | MYSQL_RANDOM_ROOT_PASSWORD: "yes" 37 | volumes: 38 | - django-db-qa:/var/lib/mysql 39 | logging: { options: { max-size: "10m", max-file: "3" } } 40 | 41 | volumes: 42 | django-db-qa: -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.4" 2 | services: 3 | webserver: 4 | image: django-docker-demo:webserver 5 | build: 6 | dockerfile: Dockerfile 7 | target: webserver 8 | context: . 9 | ports: 10 | - 8080:80 11 | logging: { options: { max-size: "10m", max-file: "3" } } 12 | django: 13 | image: django-docker-demo:dev 14 | build: 15 | dockerfile: Dockerfile 16 | target: dev 17 | context: . 18 | ports: 19 | - 8000:8000 20 | - 5678:5678 21 | volumes: 22 | - ./django_demo/:/app/ 23 | environment: 24 | DJANGO_DEBUG: "y" 25 | DJANGO_DB_HOST: "db" 26 | DJANGO_DB_DATABASE: "django" 27 | DJANGO_DB_USER: "djangouser" 28 | DJANGO_DB_PASSWORD: "djangouserpassword" 29 | logging: { options: { max-size: "10m", max-file: "3" } } 30 | 31 | celery-worker: 32 | image: django-docker-demo:latest 33 | build: 34 | dockerfile: Dockerfile 35 | context: . 36 | volumes: 37 | - ./django_demo/:/app/ 38 | command: "celery -A django_demo.tasks worker --loglevel=info" 39 | logging: { options: { max-size: "10m", max-file: "3" } } 40 | 41 | redis: 42 | image: redis:5.0-alpine 43 | logging: { options: { max-size: "10m", max-file: "3" } } 44 | 45 | db: 46 | image: mariadb:10.4 47 | restart: always 48 | environment: 49 | MYSQL_DATABASE: "django" 50 | MYSQL_USER: "djangouser" 51 | MYSQL_PASSWORD: "djangouserpassword" 52 | MYSQL_RANDOM_ROOT_PASSWORD: "yes" 53 | volumes: 54 | - django-db:/var/lib/mysql 55 | logging: { options: { max-size: "10m", max-file: "3" } } 56 | 57 | volumes: 58 | django-db: --------------------------------------------------------------------------------