Overview
The original version of my website was created for the Cloud Resume Challenge, but as I've progressed after achieving the AZ-104 certification I decided to break away from its mold a bit. I started to get more focused on Python and wanted to redo my site with Django. I also wanted to implement IAC via Terraform and CI/CD via GitHub Actions, to implement things properly.
A little overkill for a single-page site, but I thought it was a fun project and it allows me to better expand in the future.
I delved into Django a bit last summer, and IAC / CI/CD the last several months, but I want to thank the following sources for helping me get through this project:
- mdn web docs - Beginner's Guide to Django
- simple is better than complex - Another beginner's guide to Django
- Terraform: Up and Running - Excellent read on using Terraform
- Deploying Terraform at scale with GitHub Actions - A very straight-forward article by Facundo Gauna on using GitHub Actions to deploy Terraform
├───app
│ ├───cmeadows_tech # Project folder
│ ├───home # Main app folder
└───infra
├───global # Global Terraform IAC
└───web # Terraform IAC for running app
Models and Views
Pretty straightforward for a single-page site. At the moment I have one model for my projects, so in the future I can more easily add, remove and modify them. You know, your basic CRUD operations.
class Project(models.Model):
title = models.CharField(max_length=30, help_text='The title of the project')
url = models.URLField(help_text='The URL for the project\'s repo or blog post', blank=True)
tech = models.CharField(max_length=200, help_text='Comma separated list of technologies used by the project')
synopsis = models.TextField(help_text='Summary of the project')
In the future I would like to add another model for a more extended project description, akin to a blog post, so visitors can read more about my projects without having to follow through to another site.
settings.py
Cosmos MongoDB
To try and save on costs and because I like NoSQL databases, I wanted to put my database up on Cosmos DB. The easiest way to do this was with its MongoDB API and the Djongo backend.
This did come with some difficulties though, as the package dependencies were a mess after updates to Python3 and Django itself.
This open issue has an ongoing conversation on the problems I faced, and is what helped me correct my dependencies.
These packages work together:
asgiref==3.6.0
Django==4.1.4
djongo==1.3.6
dnspython==2.2.1
pymongo==3.12.3
pytz==2022.7 # This must be installed manually, it is not included in djongo
sqlparse==0.2.4
tzdata==2022.7
Note: Cosmos uses a different port than the default MongoDB port, so you have to specify it in the connection string.
Azure Storage backend
I also wanted to use Azure Storage to serve my static files, which luckily was pretty straightforward to set up with the Azure Storage backend. In the future I might look into setting up a CDN to serve these files as well.
AZURE_ACCOUNT_NAME = "<storage account name>"
AZURE_CONTAINER = 'static'
AZURE_ACCOUNT_KEY = '<storage account key>'
AZURE_OVERWRITE_FILES = 'True'
STATIC_URL = 'static/'
STATIC_DIRS = [
BASE_DIR / "static"
]
Security
Of course, this resulted in a bunch of vulnerabilities if I were to store the connection strings and keys as plaintext in settings.py, so I utilized environment variables, GitHub Secrets, and App Service Settings to secure the important bits.
DJANGO_SECRET_KEY - Used to update SECRET_KEY in production
- Secret = DJANGO_SECRET_KEY
DJANGO_DEBUG - Used to disable debug mode in production
- No Secret
DJONGO_HOST - Used to secure connection string to Cosmos MongoDB
- Secret = DJONGO_HOST
ACCOUNT_KEY - Used to secure connection key to Azure Storage hosting static files
- Secret = ACCOUNT_KEY
These can be added as Application Settings via the azure/appservice-settings GitHub Action
- uses: azure/appservice-settings@v1
with:
app-name: ${{ env.AZURE_WEBAPP_NAME }}
mask-inputs: false
app-settings-json: '[
{
"name": "ACCOUNT_KEY",
"value": "${{ secrets.ACCOUNT_KEY }}",
"slotSetting": false
},
{
"name": "DJANGO_DEBUG",
"value": "False",
"slotSetting": false
},
{
"name": "DJANGO_SECRET_KEY",
"value": "${{ secrets.DJANGO_SECRET_KEY }}",
"slotSetting": false
},
{
"name": "DJONGO_HOST",
"value": "${{ secrets.DJONGO_HOST }}",
"slotSetting": false
}
]'
general-settings-json: '{"linuxFxVersion": "PYTHON|${{ env.PYTHON_VERSION }}"}'
Terraform
I have two main Terraform configurations where I am utilizing the Azure Backend and a Service Principal for authentication, as mentioned earlier.
- global - This is used to define the resource group and storage account used by both the remote state and static files. Using the same storage account for both probably isn't considered a best practice, but I thought it acceptable for a small personal project where everything would likely share the same lifecycle
- web - This defines the infrastructure my Django app needs to run. The Cosmos DB, the static container, and the App Service
Security
For authentication, I am using a Service Principal and setting the below environment variables during the workflow run, with their values retrieved from repo secrets.
ARM_CLIENT_ID
ARM_CLIENT_SECRET
ARM_SUBSCRIPTION_ID
ARM_TENANT_ID
Pipelines
Just two pipelines at the moment
- deploy.yml - Not well-named, but is what I used to deploy my web Terraform configuration. I want to thank Facuno Gauna for his wonderful article on deploying Terraform with GitHub Actions
- appcontent.yml - Used to deploy my app content to the Azure App Service. Using this sample provided by Microsoft, with very minor modifications.
Dockerfile
I started creating a Dockerfile with the intention of containerizing, which I still might, but haven't completed due to deciding on Azure App Service hosting instead of a container instance. It really only needs environment variables for settings.py at this point, then publishing somewhere.
Top comments (0)