├── .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 !Hello world from: {}".format(
8 | os.environ.get("HOSTNAME", "no_host")
9 | )
10 |
11 | return HttpResponse(output_string)
12 |
13 |
14 | def test_task(request):
15 | task = tasks.hello.delay(os.environ.get("HOSTNAME", "no_host"))
16 | output_string = task.get(timeout=5)
17 | return HttpResponse(f"{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:
--------------------------------------------------------------------------------