In this article we’re going to explore how to segment our app into a small network of multiple Docker containers, each with their own images.
Single containers are easy enough to build imperatively in the command line, but doing anything more complicated can quickly get out of hand. Instead we’re going to use a new special type of config file called docker-compose.yml
. This declarative approach will allow us to quickly define our images in each container and setup the networking between them.
In this example we’re going to be setting up an NGINX server, a Django server.
Prerequisites
It would be helpful to know how to build images with Dockerfile, which you can brush up on here, but that will mostly be taken care of in the starter.
Make sure the following are installed in your local machine
- virtualenv - for Python local environment
- Docker
- Docker Compose
- Python - Should be installed in your system to run virtualenv.
Project Structure
├── botservice/
├── core
| ├── core/
│ │ ├── __init__.py
│ │ ├── asgi.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ ├── wsgi.py
│ │
│ └── Dockerfile
│ │
│ └── db.sqlite3
│ ├── manage.py
│ └── requirements.txt
├── nginx
│ ├── default.conf
│ └── Dockerfile
├── env/
├──docker-compose.yml
Starter Setup
Create a directory in your workspace eg.
botserver
navigate to your directorycd botserver
using the terminal.Activate virtual environment - run
virtualenv -p python env
. Then to activate virtual environment run
source env/bin/activate
Install Django into the virtual environment by running
pip install Django
it will install the latest version of Django framework.Create Django project run
django-admin startproject core
core is the name of our project. Inside the core folder wheremanage.py
is located create a file calledrequirements.txt
this is where we add our Python dependencies they will be installed when docker runs builds, in the requirements.txt file add the dependencies as shown below:
Django==3.1.1
gunicorn==20.0.4
Gunicorn 'Green Unicorn' is a Python WSGI
HTTP Server for UNIX.
NGINX Setup
The NGINX server is different than the other containers. NGINX will act as the router for the server, directing requests to the server container.
In a special configuration file, default.conf
, we’ll use upstream
to tell NGINX on what server port each the container is running.
Create a folder called
nginx
in thebotservice
folder, insidebotservice
folder in the terminal runmkdir nginx
Navigate inside the newly created folder
nginx
and create a file calleddefault.conf
and add the following code.
upstream botservice {
server botservice:7000;
}
server {
listen 80;
location /{
proxy_pass http://botservice/;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
proxy_connect_timeout 300;
proxy_send_timeout 300;
proxy_read_timeout 300;
send_timeout 300;
}
}
Now we just need docker to put this configuration somewhere more useful. The NGINX container will already have an empty default.conf
file, so copying ours to its location will override the old one.
Create a Dockerfile
inside the nginx folder add the following to the Dockerfile
FROM nginx
RUN rm -rf /usr/share/nginx/html/*
COPY ./default.conf /etc/nginx/conf.d/default.conf
We also need to add a Dockerfile for the Django server
inside the core directory where manage.py
file is located add a file called Dockerfile
then add the following.
FROM python:3.8-slim-buster
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
WORKDIR /src
COPY requirements.txt /src/
RUN pip install -r requirements.txt
COPY . .
Docker Compose
Create a file called docker-compose.yml
inside the botservice folder and add the following:
version: '3'
services:
botservice:
container_name: botservice
build:
context: ./core
command: sh -c "gunicorn --bind 0.0.0.0:7000 core.wsgi:application"
restart: always
volumes:
- "./core/:/src/"
- static_volume:/src/static
expose:
- "7000"
nginx:
container_name: nginx
restart: always
build: ./nginx
ports:
- "80:80"
volumes:
- static_volume:/src/static
depends_on:
- botservice
volumes:
static_volume:
Let’s go over exactly what this is trying to do:
-
service
- Declares each container with its particular configuration, which we can name however we like. -
build
- Tells how we want our container built, in this case which file to use and where it is with dockerfile and context. -
restart
- Tells Docker what to do if a container fails during runtime, in this case we always want it to attempt to restart. -
ports
- Remaps whatever port we want to the default port, just like the -p flag when working in the terminal. -
volumes
- Are the persistent data connected to each container. We’re duplicating parts of our container and its dependencies in a way that when we throw the container away and start a new one it’ll have that cache to avoid the time of reinstalling everything. -
depends_on
- Express dependency between services, which has two effects: -
container_name
- Specify a custom container name, rather than a generated default name. -
expose
- Expose ports without publishing them to the host machine - they’ll only be accessible to linked services. Only the internal port can be specified. -
command
- Override the default command.
More info about Docker Compose file.
Finally we can create our services and attach our containers together using the docker-compose up
command and the --build
flag to build out our Dockerfiles.
Navigate to where the docker-compose.yml
file is located via terminal and run:
docker-compose up --build
Closing Thoughts
This may have been a very simple use case, but Docker Compose is definitely one of the major tools you’ll be using with almost all of your Docker projects. You can check out a demo here to see how I did it when developing some chatbot.
Top comments (2)
we have nginx listening on port 80 ("80:80"). It turns out that I cannot create a second project on port 80 so that another project will open on another homer?
@maksam07 You just need to change the port number nginx is listening on to different one.