NOTE: This article was initially posted on my Substack, at https://andresalvareziglesias.substack.com/
Hi everyone!
In the first post of this Python/Django/Docker tutorial series, we define an idea to test these technologies with a real application: a supercool version of Tic Tac Toe with magical gems, and dragons, and a supersmart CPU player that uses IA. And sparks. A lot of sparks.
The second step in the basic project architecture is to deal with static files, because Gunicorn is a pure python server, so we need another server for this purpose.
Let’s get started!
Articles in this series
- Chapter 1: Let the journey start
- Chapter 2: Create a containerized Django app with Gunicorn and Docker
- Chapter 3: Serve Django static files with NGINX
- Chapter 4: Adding a database to our stack
- Chapter 5: Applications and sites
- Chapter 6: Using the Django ORM
- Chapter 7: Users login, logout and register
About Django static files
Every Django application has dynamic content (the Python part) and static content (images, CSS and JS files, etc).
To use the static files in our project, first we need to edit settings.py inside Django app source code folder, and set STATIC_URL and STATIC_ROOT variables:
STATIC_URL = 'static/'STATIC_ROOT = '../static'STATIC_URL = 'static/' STATIC_ROOT = '../static'STATIC_URL = 'static/' STATIC_ROOT = '../static'
Enter fullscreen mode Exit fullscreen mode
Then, download the static files with the manage.py command in app folder.:
cd tic-magical-line/app/src/ticmagicallinepython3 manage.py collectstaticcd tic-magical-line/app/src/ticmagicalline python3 manage.py collectstaticcd tic-magical-line/app/src/ticmagicalline python3 manage.py collectstatic
Enter fullscreen mode Exit fullscreen mode
The static files have been downloaded to app/src/static folder. You can use a file browser to check them all.
As you can see, these are the static files (CSS, JS and images) of the Django admin site.
Create a NGINX container to serve the static files
As we said before, Gunicorn only serves dynamic python files, so we will use NGINX to serve the static files and to give our app a single entry point.
First, we will create a dedicated folder for the server at the root of the project:
cd tic-magical-linemkdir servermkdir server/staticcd tic-magical-line mkdir server mkdir server/staticcd tic-magical-line mkdir server mkdir server/static
Enter fullscreen mode Exit fullscreen mode
Inside the server folder, create the same three files as before to define the container:
cd tic-magical-line/servertouch Dockerfiletouch environment.envtouch requirements.txtcd tic-magical-line/server touch Dockerfile touch environment.env touch requirements.txtcd tic-magical-line/server touch Dockerfile touch environment.env touch requirements.txt
Enter fullscreen mode Exit fullscreen mode
Leave environment.env and requirements.txt empty. Write the following content in Dockerfile:
FROM nginx:alpineADD ./server/default.conf /etc/nginx/conf.dADD ./app/src/static /var/www/html/staticCMD [ "nginx", "-g", "daemon off;" ]FROM nginx:alpine ADD ./server/default.conf /etc/nginx/conf.d ADD ./app/src/static /var/www/html/static CMD [ "nginx", "-g", "daemon off;" ]FROM nginx:alpine ADD ./server/default.conf /etc/nginx/conf.d ADD ./app/src/static /var/www/html/static CMD [ "nginx", "-g", "daemon off;" ]
Enter fullscreen mode Exit fullscreen mode
In the line ADD ./app/src/ticmagicalline/static /var/www/html/static, we are including the static content from the app folder.
Now, create the NGINX server configuration:
cd tic-magical-line/servertouch default.confcd tic-magical-line/server touch default.confcd tic-magical-line/server touch default.conf
Enter fullscreen mode Exit fullscreen mode
With this content:
upstream ticmagicalline {server 10.20.30.1:8081;}server {listen 8080;location / {proxy_pass http://10.20.30.1:8081;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header Host $host;proxy_redirect off;}location /static/ {alias /var/www/html/static/;}}upstream ticmagicalline { server 10.20.30.1:8081; } server { listen 8080; location / { proxy_pass http://10.20.30.1:8081; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_redirect off; } location /static/ { alias /var/www/html/static/; } }upstream ticmagicalline { server 10.20.30.1:8081; } server { listen 8080; location / { proxy_pass http://10.20.30.1:8081; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_redirect off; } location /static/ { alias /var/www/html/static/; } }
Enter fullscreen mode Exit fullscreen mode
These IPs will be each machine IP in our inner docker network, keep reading to know more.
IMPORTANT! We will change the initial port chosen in the Django app from 8080 to 8081, to allow NGINX to serve the app in the port 8080. We can also remove the exposed port in Django Dockerfile, because it is not needed anymore. And we will make a minor change in paths to Dockerfile of the Django app, to change the base path used in docker-compose build phase. You can see al the changes in the project public repository.
About traffic origins in the internal Docker network
Now the traffic to Gunicorn is redirecter internally by NGINX, as defined in these lines:
upstream ticmagicalline {server 10.20.30.1:8081;}upstream ticmagicalline { server 10.20.30.1:8081; }upstream ticmagicalline { server 10.20.30.1:8081; }
Enter fullscreen mode Exit fullscreen mode
We need to define the NGINX internal IP as an allowed domain in the Django app. Edit settings.py inside Django app source code folder, and set CSRF_TRUSTED_ORIGINS variable:
CSRF_TRUSTED_ORIGINS = ['http://10.20.30.2:8080','http://localhost:8080','http://127.0.0.1:8080']CSRF_TRUSTED_ORIGINS = [ 'http://10.20.30.2:8080', 'http://localhost:8080', 'http://127.0.0.1:8080' ]CSRF_TRUSTED_ORIGINS = [ 'http://10.20.30.2:8080', 'http://localhost:8080', 'http://127.0.0.1:8080' ]
Enter fullscreen mode Exit fullscreen mode
Glue them all with docker-compose
To join these two machines (the Gunicorn server with our Django app and the NGINX server with the static content), we will write a docker-compose file, that is a definition of a more complex system composed by several containers.
To write this docker-compose file, go to the root of the project and create the compose file:
cd tic-magical-linetouch docker-compose.ymlcd tic-magical-line touch docker-compose.ymlcd tic-magical-line touch docker-compose.yml
Enter fullscreen mode Exit fullscreen mode
Write this content inside:
version: '3.8'services:app:build:context: ./app/dockerfile: ./app/Dockerfilecontainer_name: apphostname: apprestart: alwaysvolumes:- ./app/src/:/usr/src/app/depends_on:- serverenv_file:- ./app/environment.envnetworks:django_net:ipv4_address: 10.20.30.1server:build:context: ./dockerfile: ./server/Dockerfilecontainer_name: serverhostname: serverrestart: alwaysenv_file:- ./server/environment.envports:- 8080:8080networks:django_net:ipv4_address: 10.20.30.2networks:django_net:ipam:config:- subnet: 10.20.30.0/24gateway: 10.20.30.254version: '3.8' services: app: build: context: ./app/ dockerfile: ./app/Dockerfile container_name: app hostname: app restart: always volumes: - ./app/src/:/usr/src/app/ depends_on: - server env_file: - ./app/environment.env networks: django_net: ipv4_address: 10.20.30.1 server: build: context: ./ dockerfile: ./server/Dockerfile container_name: server hostname: server restart: always env_file: - ./server/environment.env ports: - 8080:8080 networks: django_net: ipv4_address: 10.20.30.2 networks: django_net: ipam: config: - subnet: 10.20.30.0/24 gateway: 10.20.30.254version: '3.8' services: app: build: context: ./app/ dockerfile: ./app/Dockerfile container_name: app hostname: app restart: always volumes: - ./app/src/:/usr/src/app/ depends_on: - server env_file: - ./app/environment.env networks: django_net: ipv4_address: 10.20.30.1 server: build: context: ./ dockerfile: ./server/Dockerfile container_name: server hostname: server restart: always env_file: - ./server/environment.env ports: - 8080:8080 networks: django_net: ipv4_address: 10.20.30.2 networks: django_net: ipam: config: - subnet: 10.20.30.0/24 gateway: 10.20.30.254
Enter fullscreen mode Exit fullscreen mode
Now, we can build all the images:
docker compose builddocker compose builddocker compose build
Enter fullscreen mode Exit fullscreen mode
After the build phase ends, the system is ready for testing. Run all containers with:
docker-compose updocker-compose updocker-compose up
Enter fullscreen mode Exit fullscreen mode
If all goes as desired, we will see the output of the two containers, and our Django app will be accessible in http://127.0.0.1:8080, as before.
About the list
Among the Python and Docker posts, I will also write about other related topics (always tech and programming topics, I promise… with the fingers crossed), like:
- Software architecture
- Programming environments
- Linux operating system
- Etc.
If you found some interesting technology, programming language or whatever, please, let me know! I’m always open to learning something new!
About the author
I’m Andrés, a full-stack software developer based in Palma, on a personal journey to improve my coding skills. I’m also a self-published fantasy writer with four published novels to my name. Feel free to ask me anything!
暂无评论内容