Working with Multiple Containers Using Docker Compose on Linux with Django and NGINX
Tags: python, nginx, docker, django
Published: 2020-09-11 13:54:51

---
title: Working with Multiple Containers Using Docker Compose on Linux with Django and NGINX
published: true
description: Working with Multiple Containers Using Docker Compose
tags: python,nginx,docker,django
//cover_image: https://direct_url_to_image.jpg
---

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](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/), but that will mostly be taken care of in the starter.

Make sure the following are installed in your local machine
1. [virtualenv](https://pypi.org/project/virtualenv/1.6.4/)  - for Python local environment
2. [Docker](https://docs.docker.com/)
3. [Docker Compose](https://docs.docker.com/compose/install/)
4. [Python](https://www.python.org/) - Should be installed in your system to run virtualenv.

# Project Structure 

```mardown
├── 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

1. Create a directory in your workspace eg. `botserver` navigate to your directory `cd botserver` using the terminal.

2. **Activate virtual environment** - run `virtualenv -p python env`. Then to activate virtual environment run 
`source env/bin/activate`
3. Install **Django** into the virtual environment by running `pip install Django` it will install the latest version of Django framework.

4. Create Django project run `django-admin startproject core` **core** is the name of our project. Inside the core folder where `manage.py` is located create a file called `requirements.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:

```txt
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. 

1. Create a folder called `nginx` in the `botservice` folder, inside `botservice` folder in the terminal run `mkdir nginx`

2. Navigate inside the newly created folder `nginx` and create a file called `default.conf` and add the following code.

```nginx
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`

```docker

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.

```docker

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:

```yaml

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:

1. `service` - Declares each container with its particular configuration, which we can name however we like.
2. `build` - Tells how we want our container built, in this case which file to use and where it is with dockerfile and context.
3. `restart` - Tells Docker what to do if a container fails during runtime, in this case we always want it to attempt to restart.
3. `ports` - Remaps whatever port we want to the default port, just like the -p flag when working in the terminal.
4. `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.
5. `depends_on` - Express dependency between services, which has two effects:
6. `container_name` - Specify a custom container name, rather than a generated default name.
7. `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.
8. `command` - Override the default command.

[More info about Docker Compose file.](https://docs.docker.com/compose/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.](https://github.com/pmutua/botserver.git)