Introduction
This article will walk you through the process of deploying a Django + Nginx application to a VPS (Amazon Lightsail). I’ll use Docker to build and containerise the application and Ansible for automated deployments and server provisioning. To make it easy to follow along, I’ll deploy my invoice generator application which you can download from my GitHub. You’ll see how to set up AWS Lightsail instance, add a domain and use ansible to configure and deploy code to it.
Prerequisites
To follow along, you’ll need the following:
- A Dockerised Django application
- Code repo hosted on GitHub
- Ansible installed on your local machine
- A VPS server such as AWS Light Sail. AWS offers free VPS services for up to 3 months on some plans.
- A GitHub SSH Key to allow the ansible user to fetch code from GitHub
The Application
We’ll deploy a standard Django application that is served using nginx and gunicorn.
Here’s a simplified view of the code:
.
├── ansible
│ ├── ansible.cfg
│ ├── deploy.yaml
│ ├── hosts
│ ├── README.md
│ ├── roles
│ │ ├── common
│ │ │ ├── tasks
│ │ │ │ ├── checkout.yaml
│ │ │ │ └── webhook_service.yaml
│ │ │ └── templates
│ │ │ ├── set_environment.j2
│ │ │
│ │ ├── docker
│ │ │ └── tasks
│ │ │ └── main.yaml
│ │ └── security
│ │ └── tasks
│ │ └── main.yaml
│ └── setup.yaml
├── docker-compose.yml
├── Dockerfile
├── docs
│ └── README.md
├── gunicorn.conf.py
├── invoices
│ └── ...(The main app)
├── manage.py
├── nginx
│ ├── Dockerfile
│ ├── nginx.conf
│ ├── nginx-dev.conf
│ └── start_nginx.sh
├── README.md
├── requirements.txt
├── templates
│ ├── ...(template files)
- The
ansible
folder contains ansible playbooks to setup the VPS and deploy code to it. - The
nginx
folder contains nginx configurations to serve our application’s static files and handle HTTPS. gunicorn
is a Python web server that allows you to run Python web applications including Django and Flask. It serves the application via a reverse proxy defined innginx-dev.conf
ornginx-prod.conf
.
Creating the VPS
Login to AWS Lightsail. Once logged in, click the Create Instance button to create a new VPS instance and go through the following steps.
We want to use a bare-bones server, so in the “Pick your instance image“, Click “Linux/Unix” and select “OS Only” and pick an Operating system. I’ll go with Ubuntu 22.04 in this example:
Select a monthly plan and optionally, a new SSH key pair:
Give your VPS instance a name and choose “Create Instance” when you’re done:
Give the instance a few moments to initialise, after which you should see the instance up and running. You can connect to it without leaving the LightSail dashboard. In the Lightsail home page, choose the menu on the right of your instance’s name, and then choose connect:
Alternatively you can connect via the SSH client installed on your computer. To do this, take note of the IP address of the instance and SSH as the default user ubuntu. In most VPS services the default user is root. AWS Lightsail does not allow SSH root access by default:
ssh ubuntu@<your_server_ip>
exit
We’ll create a new user later using ansible.
We don’t do much manual configuration from this point, ansible will do the heavy lifting for you.
Add a domain
Purchase and point your domain to Lightsail. You need a domain to request TLS certificates from Let’s Encrypt for HTTPS. I used Route 53 to purchase and configure my domain.
Configure VPS instance using Ansible
Next, we need to setup and configure the VPS instance with the software and configuration it’ll need to run the project. We’ll use ansible to configure the server and deploy code to it. Add the newly created server instance to Ansible by creating a hosts or inventory file:
[cybertron]
invoices.vndprojects.com
[cybertron:vars]
deploy_environment=production
repo_name=terrameijar/invoices
repo_branch=develop
create_user=optimus
repo_folder="/home/{{create_user}}/invoices"
env_file=../.env
- The
create_user
variable is the name of the user account we’ll create using ansible. Change this to a username of your choice.
cybertron
is what I’m calling this server. You can give it any name you like and point it to your domain or the IP address of your VPS.repo_name
,repo_branch
andrepo_folder
are variables we’ll use later when deploying the code.
Create and copy over any .env
files you need to the VPS. For this project, this will do:
ENVIRONMENT=development
DEFAULT_FROM_EMAIL = "admin@example.com"
ALLOWED_HOSTS = localhost, 127.0.0.1
SECRET_KEY = "django-insecure-1u_jv5-k)#@cs2#)9$_@gj=0$s)p6u8vyozx!8jro_i_v!m(wq"
DEBUG = True
Ansible Playbook to setup the server
The playbook below will set up the VPS instance with the necessary packages such as git, and UFW which we’ll need later.
# ansible/setup.yaml
---
- name: Setup Server
hosts: cybertron
become: true
vars:
ansible_user: ubuntu
sys_packages: ["curl", "git", "ca-certificates", "apt-transport-https", "software-properties-common", "gnupg", "ufw"]
server_name: cybertron
copy_local_ssh_key: "{{ lookup('ansible.builtin.file', lookup('ansible.builtin.env', 'HOME') + '/.ssh/id_ed25519.pub') }}"
roles:
- role: security
- role: docker
This playbook has two roles; security
and docker
. The first role, security creates rules for the UFW firewall and creates a new SSH user which will be the replacement for the default root or ubuntu user created when the VPS instance was created. The role disables remote root logins and disallows password authentication over SSH in favour of public key authentication.
The second role, docker
installs Docker and all dependencies required to run the dockerised project.
Run Playbook
Run the playbook. For the first run, run it using the ubuntu
username created by Lightsail.
cd ansible
ansible-playbook --user ubuntu setup.yaml
If the playbook ran successfully, you should be able to SSH via the newly created user account.
ssh <your-user>@<server-ip>
SSL/TLS certificates
When the ENVIRONMENT
variable in the env file is set to development, the docker build process generates a self signed TLS certificate and configures nginx to use it. Self signed certificates are okay for testing, but In production you’ll want to use a “real” certificate from a recognised certificate authority such as Let’s Encrypt.
Request SSL certificates from Let’s Encrypt using standalone mode and copy their file paths to your Nginx config. If you like, you can automate this step using a script that can be run as an ansible role.
Deploying the application
Run the deploy.yaml
playbook to deploy the code to the VPS instance.
---
- hosts: cybertron
gather_facts: true
become_user: "{{create_user}}"
vars:
ansible_user: "{{create_user}}"
tasks:
- include_tasks: roles/common/tasks/checkout.yaml
- name: Run `docker compose up --build --detach
command:
docker compose up --build --detach
args:
chdir: "{{ repo_folder }}"
register: output
- debug:
var: output
This playbook SSHes into the server as the user you created when setting up the server, clones the code using git and brings up the docker containers.
Conclusion
By running the setup and deploy playbooks, you configured a server and deployed a Django + Nginx project to your server with HTTPS. In the next article, I’ll show you how to go an extra step and use GitHub Actions to create a CI/CD pipeline that automatically runs tests and deploys new code to the server whenever a pull request is made to the main branch.