SlideShare a Scribd company logo
Copyright 2019 © systemati.co
Docker and Django
Hendrik Frentrup

hendrik@systemati.co

31 January 2019

Auckland, NZ
Copyright 2019 © systemati.co
Ways to get in touch
hendrik@systemati.co

@HendrikFrentrup

linkedin.com/in/frentrup/

github.com/hendrikfrentrup

medium.com/@hendrik.frentrup
Copyright 2019 © systemati.co
Links
•GitHub repo for the code

•github.com/hendrikfrentrup/docker-django

•Walkthrough:

•medium.com/devopslinks/tech-edition-how-to-dockerize-a-django-web-
app-elegantly-924c0b83575d

•https://medium.com/@hendrik.frentrup/tech-edition-django-dockerization-
with-bells-and-whistles-and-a-tad-bit-of-cleverness-2b5d1b57e289

•Further reading:

•medium.com/panaseer-labs-engineering-data-science/how-devops-
helped-us-solve-our-big-data-development-problem-263474dfeedb

•Contact details:

•Email: hendrik@systemati.co

•Twitter: @hendrikfrentrup
•LinkedIn: linkedin.com/in/frentrup/
Copyright 2019 © systemati.co
The Problem
• Moving code from development environments to staging or
production can break things

• The state of environments drifts and is hard to manage

• For lack of access, developers have no insight into the specifics of
production environments
Copyright 2019 © systemati.co
From here
Application
Database
Local machine
Copyright 2019 © systemati.co
To here
Docker
web
gunicorn
server
db
postgres
Local machine
db-admin
pgadmin
server
nginx
nginx
server
python
manage.py
runserver
*.conf
+ static/
pg-data/
Copyright 2019 © systemati.co
• Move a locally running app into a container

• Setup database, admin and nginx services

• Run migrations

• Logging
• Infrastructure-as-code

• Bring development and production
environments close together

• Increased productivity
Outline
Copyright 2019 © systemati.co
Simple app running locally
python manage.py runserver
db.sqlite3
Local machine
Copyright 2019 © systemati.co
10 commands to set up
> git init
> git checkout -b develop
> virtualenv env
> source env/bin/activate
> pip install Django>=2.1
> echo 'Django>=2.1' >> requirements.txt
> django-admin startproject my-site
> git add * && git commit -m "..."
> python mysite/manage.py runserver
Copyright 2019 © systemati.co
App running in container
Docker
web
python manage.py runserver
db.sqlite3
Local machine
Copyright 2019 © systemati.co
App inside Docker
> touch Dockerfile
> docker build -t django-docker:0.0.1 .
> docker run -p 8001:8001 docker-django:0.0.1
______Dockerfile____________________________________________________________
FROM python:3.6.7-alpine
ENV PYTHONUNBUFFERED 1
RUN mkdir /code
WORKDIR /code
ADD ./ /code/
RUN pip install -r requirements.txt
CMD ["python", "mysite/manage.py", "runserver", "0.0.0.0:8001"]
Copyright 2019 © systemati.co
Success looks like …
Copyright 2019 © systemati.co
Local storage
Docker
web
python manage.py runserver
db.sqlite3db.sqlite3
Local machine
Copyright 2019 © systemati.co
Moving to docker-compose
> touch docker-compose.yml
> docker-compose up
______docker-compose.yml__________________________________________________
version: "3"
services:
web:
build: .
command: python mysite/manage.py runserver 8001
ports:
- “8001:8001”
volumes:
- ./mysite/:/code
maps ports and container code/db to local filesystem
Copyright 2019 © systemati.co
Database service
Docker
web
python
manage.py
runserver
db
postgres
Local machine
Copyright 2019 © systemati.co
Adding a database service
______docker-compose.yml__________________________________________________
version: "3"
services:
web:
build: .
command: python mysite/manage.py runserver 8001
ports:
- “8001:8001”
volumes:
- ./mysite/:/code
db:
image: postgres
networks:
- backend
networks:
backend:
driver: bridge
Copyright 2019 © systemati.co
Handling env. variables
______settings.py____________________________________________________________
POSTGRES_PASSWORD = os.environ.get('POSTGRES_PASSWORD')
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'postgres',
'USER': 'postgres',
'PASSWORD': POSTGRES_PASSWORD,
'HOST': ‘db',
'PORT': '5432',
},
______.env__________________________________________________________________
POSTGRES_PASSWORD=secret
POSTGRES_USER=postgres
DJANGO_SECRET_KEY=secret
> touch .env
> echo 'psycopg2-binary' >> requirements.txt
Copyright 2019 © systemati.co
Credentials in envvars
______docker-compose.yml__________________________________________________
version: "3"
services:
web:
[...]
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_USER: ${POSTGRES_USER}
db:
[...]
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_USER: ${POSTGRES_USER}
Copyright 2019 © systemati.co
Rebuilding the web image
______Dockerfile____________________________________________________________
FROM python:3.6.7-alpine
ENV PYTHONUNBUFFERED 1
RUN apk update && 
apk add --virtual build-deps gcc python-dev musl-dev && 
apk add postgresql-dev
RUN mkdir /code
WORKDIR /code
ADD ./ /code/
RUN pip install -r requirements.txt
Copyright 2019 © systemati.co
A bit of admin
> docker-compose exec web python mysite/manage.py migrate
> docker-compose exec web python mysite/manage.py createsuperuser
Run that every time we tear down our containers?

Note down the credentials we’ve used?
Copyright 2019 © systemati.co
Data migrations
______0001_initial.py___________________________________________________________
import os
from django.db import migrations
class Migration(migrations.Migration):
dependencies = []
def generate_superuser(apps, schema_editor):
from django.contrib.auth.models import User
DJANGO_SU_NAME = os.environ.get('DJANGO_SU_NAME')
DJANGO_SU_EMAIL = os.environ.get('DJANGO_SU_EMAIL')
DJANGO_SU_PASSWORD = os.environ.get('DJANGO_SU_PASSWORD')
superuser = User.objects.create_superuser(
username=DJANGO_SU_NAME, email=DJANGO_SU_EMAIL,
password=DJANGO_SU_PASSWORD)
superuser.save()
operations = [
migrations.RunPython(generate_superuser),
]
Copyright 2019 © systemati.co
More credentials
______docker-compose.yml__________________________________________________
version: "3"
services:
web:
[...]
environment:
DJANGO_DB_NAME: ${DJANGO_DB_NAME}
DJANGO_SU_NAME: ${DJANGO_SU_NAME}
DJANGO_SU_EMAIL: ${DJANGO_SU_EMAIL}
DJANGO_SU_PASSWORD: ${DJANGO_SU_PASSWORD}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_USER: ${POSTGRES_USER}
Copyright 2019 © systemati.co
web, db and admin services
Docker
web
python
manage.py
runserver
db
postgres
Local machine
db-admin
pgadmin server
Copyright 2019 © systemati.co
Adding a DB admin service
______docker-compose.yml__________________________________________________
[...]
db-admin:
image: dpage/pgadmin4
environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD}
depends_on:
- db
ports:
- "8080:80"
networks:
- backend
[...]
Copyright 2019 © systemati.co
Local app for debugging
Docker
web
python
manage.py
runserver
db
postgres
Local machine
db-admin
pgadmin
server
python
manage.py
runserver
Copyright 2019 © systemati.co
Run a local app alongside
______settings.py____________________________________________________________
from decouple import config
SECRET_KEY = config(‘DJANGO_SECRET_KEY')
DEBUG = config('DJANGO_DEBUG', default=False, cast=bool)
WEB_HOST = config('DJANGO_WEB_HOST', default='localhost')
DB_HOST = config('DJANGO_DB_HOST', default='localhost')
DB_NAME = config(‘DJANGO_DB_NAME’)
[...]
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'postgres',
'USER': POSTGRES_USER,
'PASSWORD': POSTGRES_PASSWORD,
'HOST': DB_HOST,
'PORT': '5432',
},
Copyright 2019 © systemati.co
Run a local app alongside
______docker-compose.yml_____________________________________
[...]
environment:
DJANGO_DEBUG: "True"
DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY}
DJANGO_DB_HOST: “db"
[...]
Copyright 2019 © systemati.co
With nginx
Docker
web
gunicorn
server
db
postgres
Local machine
db-admin
pgadmin
server
nginx
nginx
server
python
manage.py
runserver
Copyright 2019 © systemati.co
Add nginx server
______default.conf_______________________________
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://web:8001;
}
}
______settings.py________________________________
[...]
ALLOWED_HOSTS = [WEB_HOST, ‘localhost']
[...]
> echo “gunicorn” >> requirements.txt
Copyright 2019 © systemati.co
Add nginx server
______docker-compose.yml__________________________________________________
[...]
web:
command: gunicorn mysite.wsgi --bind 0.0.0.0:8001
[...]
nginx:
image: nginx:latest
ports:
- 8088:80
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
networks:
- backend
depends_on:
- web
[...]
Copyright 2019 © systemati.co
Static files & persistent data
Docker
web
gunicorn
server
db
postgres
Local machine
db-admin
pgadmin
server
nginx
nginx
server
python
manage.py
runserver
*.conf
+ static/
pg-data/
Copyright 2019 © systemati.co
Serving static assets
______settings.py_______________________________________
[...]
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
> python mysite/manage.py collectstatic
______default.conf_____________________
server {
[...]
location /static {
alias /code/static;
}
}
Copyright 2019 © systemati.co
Serving static assets
______docker-compose.yml__________________________________________________
[...]
nginx:
[...]
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
- ./mysite/static:/code/static
[...]
db:
volumes:
- ./postgres-data:/var/lib/postgresql/data
[...]
Copyright 2019 © systemati.co
Mapped ports
Docker
web
8001
db
5432
Local machine
db-admin
8080
nginx
8088
8000
Copyright 2019 © systemati.co
Logs
Copyright 2019 © systemati.co
Logging locally
• To STDOUT!
Copyright 2019 © systemati.co
Logs in shared envs
• Complex topic and no one size fits all

• Log aggregator (SumoLogic, Splunk, ELK etc.)

• A complete ELK stack in one container

• Various logging driver options

• Fluentd

• Gelf
Log aggregator
Fluentd
LogStash
ElasticSearch
Kibana
Copyright 2019 © systemati.co
Logging
Local machine
Staging environment
Build
server
Log aggregator
Production
Code
repo
Local machine
push
trigger
tail
deploy
stream
deploy
stream
Copyright 2019 © systemati.co
That’s all!
Thanks

More Related Content

Dockerize a Django app elegantly

  • 1. Copyright 2019 © systemati.co Docker and Django Hendrik Frentrup hendrik@systemati.co 31 January 2019 Auckland, NZ
  • 2. Copyright 2019 © systemati.co Ways to get in touch hendrik@systemati.co @HendrikFrentrup linkedin.com/in/frentrup/ github.com/hendrikfrentrup medium.com/@hendrik.frentrup
  • 3. Copyright 2019 © systemati.co Links •GitHub repo for the code •github.com/hendrikfrentrup/docker-django •Walkthrough: •medium.com/devopslinks/tech-edition-how-to-dockerize-a-django-web- app-elegantly-924c0b83575d •https://medium.com/@hendrik.frentrup/tech-edition-django-dockerization- with-bells-and-whistles-and-a-tad-bit-of-cleverness-2b5d1b57e289 •Further reading: •medium.com/panaseer-labs-engineering-data-science/how-devops- helped-us-solve-our-big-data-development-problem-263474dfeedb •Contact details: •Email: hendrik@systemati.co •Twitter: @hendrikfrentrup •LinkedIn: linkedin.com/in/frentrup/
  • 4. Copyright 2019 © systemati.co The Problem • Moving code from development environments to staging or production can break things • The state of environments drifts and is hard to manage • For lack of access, developers have no insight into the specifics of production environments
  • 5. Copyright 2019 © systemati.co From here Application Database Local machine
  • 6. Copyright 2019 © systemati.co To here Docker web gunicorn server db postgres Local machine db-admin pgadmin server nginx nginx server python manage.py runserver *.conf + static/ pg-data/
  • 7. Copyright 2019 © systemati.co • Move a locally running app into a container • Setup database, admin and nginx services • Run migrations • Logging • Infrastructure-as-code • Bring development and production environments close together • Increased productivity Outline
  • 8. Copyright 2019 © systemati.co Simple app running locally python manage.py runserver db.sqlite3 Local machine
  • 9. Copyright 2019 © systemati.co 10 commands to set up > git init > git checkout -b develop > virtualenv env > source env/bin/activate > pip install Django>=2.1 > echo 'Django>=2.1' >> requirements.txt > django-admin startproject my-site > git add * && git commit -m "..." > python mysite/manage.py runserver
  • 10. Copyright 2019 © systemati.co App running in container Docker web python manage.py runserver db.sqlite3 Local machine
  • 11. Copyright 2019 © systemati.co App inside Docker > touch Dockerfile > docker build -t django-docker:0.0.1 . > docker run -p 8001:8001 docker-django:0.0.1 ______Dockerfile____________________________________________________________ FROM python:3.6.7-alpine ENV PYTHONUNBUFFERED 1 RUN mkdir /code WORKDIR /code ADD ./ /code/ RUN pip install -r requirements.txt CMD ["python", "mysite/manage.py", "runserver", "0.0.0.0:8001"]
  • 12. Copyright 2019 © systemati.co Success looks like …
  • 13. Copyright 2019 © systemati.co Local storage Docker web python manage.py runserver db.sqlite3db.sqlite3 Local machine
  • 14. Copyright 2019 © systemati.co Moving to docker-compose > touch docker-compose.yml > docker-compose up ______docker-compose.yml__________________________________________________ version: "3" services: web: build: . command: python mysite/manage.py runserver 8001 ports: - “8001:8001” volumes: - ./mysite/:/code maps ports and container code/db to local filesystem
  • 15. Copyright 2019 © systemati.co Database service Docker web python manage.py runserver db postgres Local machine
  • 16. Copyright 2019 © systemati.co Adding a database service ______docker-compose.yml__________________________________________________ version: "3" services: web: build: . command: python mysite/manage.py runserver 8001 ports: - “8001:8001” volumes: - ./mysite/:/code db: image: postgres networks: - backend networks: backend: driver: bridge
  • 17. Copyright 2019 © systemati.co Handling env. variables ______settings.py____________________________________________________________ POSTGRES_PASSWORD = os.environ.get('POSTGRES_PASSWORD') DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'postgres', 'USER': 'postgres', 'PASSWORD': POSTGRES_PASSWORD, 'HOST': ‘db', 'PORT': '5432', }, ______.env__________________________________________________________________ POSTGRES_PASSWORD=secret POSTGRES_USER=postgres DJANGO_SECRET_KEY=secret > touch .env > echo 'psycopg2-binary' >> requirements.txt
  • 18. Copyright 2019 © systemati.co Credentials in envvars ______docker-compose.yml__________________________________________________ version: "3" services: web: [...] environment: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_USER: ${POSTGRES_USER} db: [...] environment: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_USER: ${POSTGRES_USER}
  • 19. Copyright 2019 © systemati.co Rebuilding the web image ______Dockerfile____________________________________________________________ FROM python:3.6.7-alpine ENV PYTHONUNBUFFERED 1 RUN apk update && apk add --virtual build-deps gcc python-dev musl-dev && apk add postgresql-dev RUN mkdir /code WORKDIR /code ADD ./ /code/ RUN pip install -r requirements.txt
  • 20. Copyright 2019 © systemati.co A bit of admin > docker-compose exec web python mysite/manage.py migrate > docker-compose exec web python mysite/manage.py createsuperuser Run that every time we tear down our containers? Note down the credentials we’ve used?
  • 21. Copyright 2019 © systemati.co Data migrations ______0001_initial.py___________________________________________________________ import os from django.db import migrations class Migration(migrations.Migration): dependencies = [] def generate_superuser(apps, schema_editor): from django.contrib.auth.models import User DJANGO_SU_NAME = os.environ.get('DJANGO_SU_NAME') DJANGO_SU_EMAIL = os.environ.get('DJANGO_SU_EMAIL') DJANGO_SU_PASSWORD = os.environ.get('DJANGO_SU_PASSWORD') superuser = User.objects.create_superuser( username=DJANGO_SU_NAME, email=DJANGO_SU_EMAIL, password=DJANGO_SU_PASSWORD) superuser.save() operations = [ migrations.RunPython(generate_superuser), ]
  • 22. Copyright 2019 © systemati.co More credentials ______docker-compose.yml__________________________________________________ version: "3" services: web: [...] environment: DJANGO_DB_NAME: ${DJANGO_DB_NAME} DJANGO_SU_NAME: ${DJANGO_SU_NAME} DJANGO_SU_EMAIL: ${DJANGO_SU_EMAIL} DJANGO_SU_PASSWORD: ${DJANGO_SU_PASSWORD} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_USER: ${POSTGRES_USER}
  • 23. Copyright 2019 © systemati.co web, db and admin services Docker web python manage.py runserver db postgres Local machine db-admin pgadmin server
  • 24. Copyright 2019 © systemati.co Adding a DB admin service ______docker-compose.yml__________________________________________________ [...] db-admin: image: dpage/pgadmin4 environment: PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL} PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD} depends_on: - db ports: - "8080:80" networks: - backend [...]
  • 25. Copyright 2019 © systemati.co Local app for debugging Docker web python manage.py runserver db postgres Local machine db-admin pgadmin server python manage.py runserver
  • 26. Copyright 2019 © systemati.co Run a local app alongside ______settings.py____________________________________________________________ from decouple import config SECRET_KEY = config(‘DJANGO_SECRET_KEY') DEBUG = config('DJANGO_DEBUG', default=False, cast=bool) WEB_HOST = config('DJANGO_WEB_HOST', default='localhost') DB_HOST = config('DJANGO_DB_HOST', default='localhost') DB_NAME = config(‘DJANGO_DB_NAME’) [...] DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'postgres', 'USER': POSTGRES_USER, 'PASSWORD': POSTGRES_PASSWORD, 'HOST': DB_HOST, 'PORT': '5432', },
  • 27. Copyright 2019 © systemati.co Run a local app alongside ______docker-compose.yml_____________________________________ [...] environment: DJANGO_DEBUG: "True" DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY} DJANGO_DB_HOST: “db" [...]
  • 28. Copyright 2019 © systemati.co With nginx Docker web gunicorn server db postgres Local machine db-admin pgadmin server nginx nginx server python manage.py runserver
  • 29. Copyright 2019 © systemati.co Add nginx server ______default.conf_______________________________ server { listen 80; server_name localhost; location / { proxy_pass http://web:8001; } } ______settings.py________________________________ [...] ALLOWED_HOSTS = [WEB_HOST, ‘localhost'] [...] > echo “gunicorn” >> requirements.txt
  • 30. Copyright 2019 © systemati.co Add nginx server ______docker-compose.yml__________________________________________________ [...] web: command: gunicorn mysite.wsgi --bind 0.0.0.0:8001 [...] nginx: image: nginx:latest ports: - 8088:80 volumes: - ./nginx/default.conf:/etc/nginx/conf.d/default.conf networks: - backend depends_on: - web [...]
  • 31. Copyright 2019 © systemati.co Static files & persistent data Docker web gunicorn server db postgres Local machine db-admin pgadmin server nginx nginx server python manage.py runserver *.conf + static/ pg-data/
  • 32. Copyright 2019 © systemati.co Serving static assets ______settings.py_______________________________________ [...] STATIC_ROOT = os.path.join(BASE_DIR, 'static') > python mysite/manage.py collectstatic ______default.conf_____________________ server { [...] location /static { alias /code/static; } }
  • 33. Copyright 2019 © systemati.co Serving static assets ______docker-compose.yml__________________________________________________ [...] nginx: [...] volumes: - ./nginx/default.conf:/etc/nginx/conf.d/default.conf - ./mysite/static:/code/static [...] db: volumes: - ./postgres-data:/var/lib/postgresql/data [...]
  • 34. Copyright 2019 © systemati.co Mapped ports Docker web 8001 db 5432 Local machine db-admin 8080 nginx 8088 8000
  • 35. Copyright 2019 © systemati.co Logs
  • 36. Copyright 2019 © systemati.co Logging locally • To STDOUT!
  • 37. Copyright 2019 © systemati.co Logs in shared envs • Complex topic and no one size fits all • Log aggregator (SumoLogic, Splunk, ELK etc.) • A complete ELK stack in one container • Various logging driver options • Fluentd • Gelf Log aggregator Fluentd LogStash ElasticSearch Kibana
  • 38. Copyright 2019 © systemati.co Logging Local machine Staging environment Build server Log aggregator Production Code repo Local machine push trigger tail deploy stream deploy stream
  • 39. Copyright 2019 © systemati.co That’s all! Thanks