This article explains how to deploy web applications to DigitalOcean App Platform.
For beginners in web development, the biggest obstacle they face on the road to publishing their own apps is deployment.
I have been using DigitalOcean since 5-6 years ago, and at that time, I used their VPS called Droplet for deployment.
Although Droplet is affordable and perfect for individual development, it is not a service that focuses on deploying and operating applications (PaaS), so I need to configure web servers and databases from scratch. This is very cumbersome, and I remember struggling for several days until these settings were successful during my first deployment.
However, since the release of App Platform in DigitalOcean in 2020 , my deployment work has become much easier.
Although App Platform is slightly more expensive than Droplet, it is a service that more than justifies its cost.
This time, to avoid struggling with deployments for those new to web development with Django, I would like to show you a stress-free and quick deployment method using the DigitalOcean App Platform.
Additionally, note that I use Docker in my development environment, but this article introduces a deployment method that does not use Docker.
I have tried several ways to deploy using Docker, but none of them worked. On the other hand, the method without docker succeeded in deploying easily, so we will introduce the method without docker in this issue.
How to Deploy a Django App to the App Platform
Let's first examine the local Docker setup.
1. Setting up Django Locally Using Docker
The process of configuring Docker and Docker Compose files to set up Django is similar to that described in this article.
We will examine the two accompanying files, as well as the .env
file:
The name of the workdir can be arbitrary.
FROM python:3.9
ENV PYTHONUNBUFFERED 1
WORKDIR /blog
COPY requirements.txt /blog/requirements.txt
RUN pip install -r requirements.txt
COPY . /blog
CMD python manage.py runserver 0.0.0.0:8011
The backend
, db_blog_docker
can be arbitrary name and container_name
and volumes
are arbitrary name as well.
version: "3.9"
services:
backend:
container_name: blog_docker
build: .
volumes:
- .:/blog_docker
ports:
- 8011:8011
depends_on:
- db_blog_docker
db_blog_docker:
container_name: blog_docker
image: postgres
restart: always
volumes:
- .dbdata:/var/lib/blog_docker
ports:
- 5441:5432
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
POSTGRES_DB=postgres
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
2. Rewrite the Setup to Deploy without Docker
Assuming that Docker is not used in the app platform, we need to modify the default settings.py
file to meet this requirement.
To do so, create a settings
folder in the project directory and divide the settings.py
file into three separate files. You can rename the existing settings.py
file to something like old_settings.py
.
Each of the three separate settings files has a specific role to play:
* base.py
- the base settings file
* dev.py
- the settings file for development (this is not pushed to production)
* prod.py
- the settings file for the production environment
The accompanying code for each file is provided below. In base.py
, the comment section has been removed.
from pathlib import Path
import environ
import os
from urllib.parse import urlparse
env = environ.Env()
BASE_DIR = Path(__file__).resolve().parent.parent.parent
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))
SECRET_KEY = os.getenv('SECRET_KEY')
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog'
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django_user_agents.middleware.UserAgentMiddleware',
]
ROOT_URLCONF = 'myproject.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.template.context_processors.request',
],
},
},
]
WSGI_APPLICATION = 'myproject.wsgi.application'
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
]
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
from pathlib import Path
import os
import environ
DEBUG = True
env = environ.Env()
BASE_DIR = Path(__file__).resolve().parent.parent.parent
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))
ALLOWED_HOSTS = []
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': os.getenv('DB_NAME'),
'USER': os.getenv('DB_USER'),
'PASSWORD': os.getenv('DB_PASSWORD'),
'HOST': os.getenv('DB_HOST'),
'PORT': os.getenv('DB_PORT'),
}
}
import os
from urllib.parse import urlparse
from pathlib import Path
DEBUG = False
BASE_DIR = Path(__file__).resolve().parent.parent.parent
ALLOWED_HOSTS = os.getenv("DJANGO_ALLOWED_HOSTS", "127.0.0.1,localhost").split(",")
DATABASE_URL = os.getenv('DATABASE_URL', None)
if not DATABASE_URL:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
else:
db_info = urlparse(DATABASE_URL)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'db',
'USER': db_info.username,
'PASSWORD': db_info.password,
'HOST': db_info.hostname,
'PORT': db_info.port,
'OPTIONS': {'sslmode': 'require'},
}
}
DB_NAME=postgres
DB_USER=postgres
DB_PASSWORD=postgres
DB_HOST=db_blog_docker
DB_PORT=5432
...
Dividing the file into three parts alone is not sufficient. To ensure that these files work correctly, you need to create an __init__.py
file in the folder and include the following code:
This step is crucial.
from .base import *
if os.getenv('myproject') == 'prod':
from .prod import *
else:
from .dev import *
Of course, you need the requirements.txt
file to run the application. For reference, I have summarized below the minimum package list used in this app.
Please note that the package whitenoise
is included. It solves various problems related to serving static files. In my experience, static files are often handled well when installing with this package. Therefore, adding this package is highly recommended.
django==3.2.10
psycopg2-binary==2.9.2
django-environ==0.8.1
whitenoise==5.3.0
In addition, the settings.py
file above relies on environment variables, so you need a .env
file to run it in a local environment. This article provides guidance on how to create the .env
file.
After completing the basic configuration of settings.py
, proceed to include the accompanying code snippet in the .gitignore
file.
...
#settings
myproject/settings/dev.py
myproject/old_settings.py
# docker file
Dockerfile
Dockerfile.prod
docker-compose.yaml
docker-compose.prod.yaml
entrypoint.sh
...
3.Set up the digital ocean App platform
Since deploying in the standard way to the App platform is also written in other blogs and documents, I would like to explain the process in a way that is consistent with our current environment.
There are five steps to deployment.
- Create an account of DigitalOcean, choose AppPlatform, and connect to GitHub account.
- Select the github repository to deploy to
- Choose whether to use the database or not
- Enter environment variables in the admin panel
- Deploy
If you look at the admin panel of DigitalOcean, you can easily understand 1 and 2, but 3 and 4 need a little explanation.
Concerning step 3, in the local environment, you need to enter settings about database such as the password and hostname. However, in the App platform, it is unnecessary since the settings are applied automatically.
Regarding step 4, in the local environment, you set environment variables in a .env
file. In contrast, in the App platform, there is a field to enter these variables in the admin panel. Therefore, you can directly enter the values in the admin panel.
The following diagram provides an overview of the deployment process:
We will push all files within the Application folder. However, we will create a settings
folder instead of the settings.py
file. This allows us to separate what we need to push and what we do not.
Also, since we will not be using Docker this time, we will not be pushing the Dockerfile
and docker-compose.yaml
files.
environment variables
After logging in to DigitalOcean, configuring the App Platform (steps 1-3 above) is easy as it proceeds in a tutorial format. The step that needs explanation is #4, "Setting environment variables."
To set environment variables, go to the "settings" tab or select "Manage Env Vars" from the Actions button on the upper right side of the screen. This will take you to the environment variable editing screen.
Write the variable name on the left and the actual value on the right.
There are three things to set: myproject
, DJANGO_ALLOWED_HOSTS
, DATABASE_URL
.
myproject
is the environment variable I set in init.py in the settings folder. please set the value prod
.
DJANGO_ALLOWED_HOSTS
Please input ${APP_DOMAIN}
.
DATABASE_URL
If you select "db" from App Platform Settings -> Components, you can check the item called "Connection Details". Here, you should be able to see the database name. Since the name "db" is set by default, the value of the environment variable here is ${db.DATABASE_URL}.
Please note that changing the name of this database will naturally change the value of the environment variable.
Once these are configured, the last step, build and deploy, will begin!
Summary of the Deploy Process
This concludes the tutorial on deploying a Django application. The key points from the above tutorial are summarized below.
- Use the Django package
whitenoise
to serve static files during deployment. When not installed, errors in static file processing can occur. Be sure to include this package when deploying. - Create a
settings
folder for deployment, and divide the settings file intobase.py
,dev.py
, andprod.py
. Include an__init__.py
file in the folder to separate the development and production settings files. Rename the originalsettings.py
file used during development to something likeold_settings.py
. - When deploying on an AppPlatform, set environment variables from the management screen. This involves reading the settings file and setting environment variables for the database host and URL.
If you've tried it and it doesn't work for some reason,Please review the above three points carefully. I'm sure it will work.
With the emergence of Platform as a Service (PaaS), deploying applications has become much easier. Let's take advantage of these services and release more and more original services!