License: GPLv3
African Cities Lab website
This setup is based on cookiecutter-django. See its documentation for more information.
- GNU Make
- terraform
- git >=2.28.0
- pre-commit
Optional (recommended):
- GitHub CLI (if you want to create the GitHub repository from the terminal).
- A DigitalOcean account. You can sign up using my referral link to get $100 in credit.
- A GitHub account.
- A Terraform Cloud account and a Terraform Cloud organization. With an active account, you can create an organization by navigating to app.terraform.io/app/organizations/new. You can also use an existing organization. This workflow is compatible with the free plan.
- A domain name, which can be from any domain registrar, but must point to the DigitalOcean nameservers. The latter can be achieved following the instructions docs.digitalocean.com/tutorials/dns-registrars.
The cookiecutter-django-doge 🐕 workflow requires the following access tokens, which can be created as listed below - alternatively, existing tokens may be used:
- DigitalOcean:
- API token: navigate to cloud.digitalocean.com/account/api/token/new (you must be authenticated), choose a name and an expiration, click on "Generate Token" and copy the generated token as the value of the
do_token
variable. - Spaces access keys: navigate to cloud.digitalocean.com/account/api, scroll down to the "Spaces access keys" section, click on "Generate New Key", provide an informative name and copy the generated key and secret as the values of the
do_spaces_access_id
anddo_spaces_secret_key
respectively.
- API token: navigate to cloud.digitalocean.com/account/api/token/new (you must be authenticated), choose a name and an expiration, click on "Generate Token" and copy the generated token as the value of the
- GitHub: navigate to github.com/settings/tokens/new (you must be authenticated), choose a name, an expiration and select at least the
repo
,read:org
andread:discussion
permissions (anddelete_repo
if you want to be able to delete the repository from Terraform). Click on "Generate token" and copy the generated token as the value of thegh_token
variable. - Terraform Cloud: navigate to app.terraform.io/app/settings/tokens and click on "Create an API token", provide a description, click on "Create API token" and copy the generated token as the value of the
tf_api_token
variable.
The access tokens (generated or exising) must be set in the terraform/deploy/meta/vars.tfvars
and terraform/deploy/dotenv/vars.tfvars
(as terraform variables), as well as in .envs/.production/.django
(environment variables) as follows:
# terraform/deploy/meta/vars.tfvars
...
# tokens
do_token = "<token-contents>" # DigitalOcean API token
do_spaces_access_id = "<token-contents>" # DigitalOcean spaces access key ID
do_spaces_secret_key = "<token-contents>" # DigitalOcean spaces secret key
gh_token = "<token-contents>" # GitHub personal access token
tf_api_token = "<token-contents>" # Terraform Cloud API token
# terraform/deploy/dotenv/vars.tfvars
...
# tokens
tf_api_token = "<token-contents>" # Terraform Cloud API token
# .envs/.production/.django
# there is no need to use quotes here (unless the values contain whitespaces)
DJANGO_AWS_ACCESS_KEY_ID=<token-contents> # DigitalOcean spaces access key ID
DJANGO_AWS_SECRET_ACCESS_KEY=<token-contents> # DigitalOcean spaces secret key ID
The initial infrastructure provisioning in the cookiecutter-django-doge workflow is done by running Terraform locally with the help of GNU Make. This will set up the required GitHub infrastructure (notably repository secrets) so that the rest of the workflow is fully managed by GitHub Actions.
From the root of the generated project, use the following command to provision the meta workspace (i.e., a workspace to manage workspaces1, 2):
make init-meta
At this point, if you navigate to app.terraform.io/app/exaf-epfl/workspaces, a workspace named african-cities-lab-org-meta
should appear.
You can then plan and apply the Terraform setup as follows:
make plan-meta
make apply-meta
which will create four additional workspaces, named african-cities-lab-org-base
, african-cities-lab-org-dotenv
, african-cities-lab-org-stage
and african-cities-lab-org-prod
.
The GitHub repository can be created in two ways:
-
using the GitHub CLI (recommended): first, make sure that you are properly authenticated with the GitHub CLI (use the
gh auth login
command). Then, from the root of the generated project, run:make create-repo
which will automatically initialize a git repository locally, add the first commit, and push it to a GitHub repository at
martibosch/african-cities-lab-org
. -
manually from the GitHub web interface: navigate to github.com/new, create a new empty repository at
martibosch/african-cities-lab-org
. Then, from the root of the generated project, initialize a git repository, setup pre-commit for the repository, add the first commit and push it to the new GitHub repository as follows:git init --initial-branch=main # this only works for git >= 2.28.0 pre-commit install git add . SKIP=terraform_validate git commit -m "first commit" git branch -M main git remote add origin [email protected]:martibosch/african-cities-lab-org git push -u origin main
Once the initial commit has been pushed to GitHub, use GNU Make to provision some base infrastructure:
make init-base
make plan-base
make apply-base
notably, a ssh key will be created and added to terraform, DigitalOcean (you can see a new item named african-cities-lab-org
at cloud.digitalocean.com/account/security, and repository secrets (you can see a repository secret named SSH_KEY
at github.com/martibosch/african-cities-lab-org/settings/secrets/actions). Additionally, a DigitalOcean project (an item named african-cities-lab-org
visible in the top-left "PROJECTS" menu of the web interface) will be created to group the resources used for this app.
The docker-compose setup uses a set of environment variables for each environment (local, staging and production) defined in the .envs
directory, whose files are provisioned by Terraform as repository secrets (file contents encoded as base64 strings) so that they can be used in GitHub Actions. This is done in the african-cities-lab-org-dotenv
workspace, which unlike the other workspaces, is executed locally so that it can read the contents of the .envs/.staging
and .envs/.production
directories, which as noted above, are kept outside version control to prevent disclosing sensitive information. The provisioning is also be done using GNU Make following the Terraform init-plan-apply scheme:
make init-dotenv
make plan-dotenv
make apply-dotenv
Once the above commands are executed, a map of the .env
files and their base64-encoded contents will be defined as the env_file_map
workspace variable in both the staging and production workspaces, i.e., app.terraform.io/app/exaf-epfl/workspaces/african-cities-lab-org-stage/variables and app.terraform.io/app/exaf-epfl/workspaces/african-cities-lab-org-prod/variables respectively.
The inital provisioning of the staging and production infrastructure must also be done using GNU Make following the Terraform init-plan-apply scheme, i.e., for the staging environment:
make init-stage
make plan-stage
make apply-stage
and for production:
make init-prod
make plan-prod
make apply-prod
If you navigate to cloud.digitalocean.com and select the african-cities-lab-org
project, you will see that droplets named african-cities-lab-org-stage
and african-cities-lab-org-prod
have been created for each environment respectively. Additionally, at github.com/martibosch/african-cities-lab-org/settings/secrets/actions), you will find the following environment secrets:
DROPLET_HOST
: IPv4 address of the staging and production hosts respectively.PROD_DJANGO
,PROD_POSTGRES
andPROD_TRAEFIK
: base64-encoded strings with the contents of the files at.envs/.production
, which will be set for both thestage
andprod
environments.STAGE_DJANGO
andSTAGE_TRAEFIK
: base64-encoded strings with the contents of the files at.envs/.staging
, which will be set for thestage
environment only.
Once the initial infrastructure has been provisioned, CI/CD is ensured by the following GitOps workflow:
- New features are pushed into a dedicated feature branch.
- develop: a pull request (PR) to the
develop
branch is created, at which point CI workflow is run. If the CI workflow passes, the PR is merged, otherwise, fixes are provided in the feature branch until the CI workflow passes. - stage: once one or more feature PR are merged into the
develop
branch, they can be deployed to the staging environment by creating a PR to thestage
branch, which will trigger the "plan" workflow. If successful, the PR is merged, at which point the "deploy" workflow is run, which will deploy the branch contents to the staging environment. - main: after a successful deployment to staging, a PR from the stage to the main branch will trigger the "plan" workflow, yet this time for the production environment. Likewise, If the workflow passes, the PR can be merged, which will trigger the "deploy" workflow, which will deploy the branch contents to production.
Overall, the Doge 🐕 GitOps workflow can be represented as follows:
gitGraph:
commit id:"some commit"
branch stage
branch develop
branch some-feature
checkout some-feature
commit id:"add feature"
checkout develop
merge some-feature tag:"CI (lint, build)"
checkout stage
merge develop tag:"deploy stage"
checkout main
merge stage tag:"deploy prod"
The infrastructure provisioned by this setup can be destroyed using GNU Make as follows:
make destroy-prod
make destroy-stage
make destroy-dotenv
make destroy-base
make destroy-meta
1. "Managing Workspaces With the TFE Provider at Scale Factory"
2. response by chrisarcand in "Using variables with remote backend"