Django on Google Cloud Run
In this tutorial, we will create a fully containerized django app locally (connected to a remote Cloud SQL postgres instance) and deploy it to Cloud Run with CI/CD, credentials management, and static file hosting.
Introduction
I recently had to deploy a django app on GCP and there were quite a few options:
- Google Cloud Functions (~ AWS Lambda)
- Google App Engine (~ AWS Beanstalk)
- Google Compute Engine (~ AWS EC2)
- Google Cloud Run
- Google Kubernetes Engine (~ AWS EKS)
Coming from AWS, I found the App Engine to be the most suitable for deploying an API service as it’s similar to how Beanstalk works. I also considered Cloud Functions (similar to AWS Lambda) but my use case was slightly complex with the need of defining models, performing migrations, and so on.
GKE was another option but I wanted to focus on launching the app first before worrying about k8s artifacts. It finally came down to App Engine and Cloud Run.
App Engine
- Fully managed PaaS from GCP (launched in 2008), battle tested for deploying planet-scale applications
- Supports Python, node, and other popular runtimes
- Code is stored in a Cloud Storage bucket
- Auto scaling based on traffic
- Out-of-the-box firewall, traffic splitting, and other key features
Cloud Run
- New GCP offering (launched in 2019) that brings the best of both worlds — serverless and containers
- Supports any runtime/language/library
- Apps are deployed as containers that can scale up or down based on configuration
- Code is stored as images on GCR that can be ported to GKE
- Integrated logging, monitoring, and CI/CD
Although both have a lot of overlap in terms of features, it’s the container aspect of writing and running applications that led me to Cloud Run in the end.
What we are going to build
We are going to deploy a django app on Cloud Run with:
- A Cloud SQL instance (postgres)
- CI/CD with Cloud Build
- Storing credentials in Secret Manager
- Serving static files with Google Cloud Storage
- Database for local development with Cloud SQL proxy connection
Step 0 — GCP setup
Before we can start django development, we will need to provision certain resources on GCP.
I. Create a new project
Projects are a way of organizing resources on GCP. Head to your GCP console and create a new project e.g. django-cloudrun-project
. It's a standard practice to create one project for dev and another for production.
II. Make sure the project has a billing account attached
III. Download GCP CLI from here
IV. Create a service account
As a best practice, we will create a new service account on GCP to run our Cloud Run instance. Go to the GCP console → IAM & Admin → Service Accounts and create a new service account e.g. svc_app_dev
Next, grant the following permissions to it:
- Cloud SQL Client
- Service Account User
- Cloud Run Invoker
- Cloud Run Service Agent
- Secret Manager Secret Accessor
- Storage Object Admin
Next, go to the KEYS section. Create a new key and store the JSON file as creds.json
, we will need it later.
V. Create a Cloud SQL instance
Although we can create a database instance using GCP CLI, it’s helpful to use the GUI if doing for the first time. Head to your GCP console → SQL → Create Instance.
Select a Postgres instance with required capacity e.g. 3.75 GB memory and 10 GB SSD. Give an apt name e.g. django-dev-db
. GCP will take up to 5 minutes to create a new instance, it will also create a default user postgres
and a default database postgres
.
Next, create a new user named djangodevdbadmin
and a new database called djangodevdb
. Make sure to note down the password for djangodevdbadmin
.
VI. Create a storage bucket
We will need to create a Google Cloud Storage bucket to host the static files from django. Head to GCP console → Cloud Storage → Create bucket, name it django_dev_bucket
.
Step 1 — Local django setup
In this section, we will create a brand new django app from scratch. We will be using docker to run the app locally (through docker compose).
requirements.txt
Dockerfile
docker-compose.yml
Next, start a new django app named app in src
directory.
docker compose run --rm app sh -c "django-admin startproject app ."
Finally, run the app.
# Keep this command window running while development
# Open a new terminal window to run additional commands in the next sections docker compose up
You should see the django welcome screen on 127.0.0.1:8000
We are not running command from docker compose because the final deployment to Cloud Run will only use the Dockerfile and so, no option to use compose there.
Step 2 — Setting up env variables
We will be setting up two kinds of environment files:
- .env for local development
- .env.prod for production
.env
.env.prod
We don’t need proxy in production as it will be taken care by GCP’s network infrastructure. We also don’t need application credentials in production as the service account we created earlier would have all the necessary permissions.
Next, we will store the contents of .env.prod in GCP Secret Manager, which is a secure way of storing sensitive information. We’ll achieve this using the GCP CLI.
At this point, our production app on Cloud Run can fetch the secret values directly from the Secret Manager instance.
Step 3 — Update settings.py
You should now be able to perform migrations and collect static files.
docker compose run --rm app sh -c "python manage.py makemigrations" docker compose run --rm app sh -c "python manage.py migrate" docker compose run --rm app sh -c "python manage.py collectstatic --no-input"
Step 4 — Create superuser
Create a superuser to access the admin dashboard.
docker compose run --rm app sh -c "python manage.py createsuperuser --username=<USER> --email=<EMAIL>"
If your cloud proxy was running locally, the superuser credentials would be stored in the remote CloudSQL instance. Commit the changes and push to your GitHub repo.
Step 5 — Create a new Cloud Run service
It’s time to deploy the application and we will use the GCP console to do so. Once comfortable with the process, you may use the GCP CLI to deploy the future ones.
Head to GCP console → Cloud Run → Create Service. Give it a name and select a region.
Since this is a tutorial app, you can keep the region same as the CloudSQL one to reduce data transfer costs.
In the next step, select SET UP WITH CLOUD BUILD option and connect your GitHub repo, branch to build off, and build type.
In the Advanced settings section, change the port to 8000. You can also set up your container configurations, concurrency settings, and autoscaling parameters.
Next, under the CONNECTIONS tab, add your CloudSQL instance.
Finally, under the SECURITY tab, add the service account (svc_app_dev
) created in step 0.
Cloud Run will take 2–5 minutes to deploy the app and return an autogenerated URL to access the app.
Step 6 — Update Cloud Build CI/CD
We will add some django-specific steps in the CI/CD process.
In the root of your project, create a new cloudbuild.yaml
file.
Commit and push your changes.
Head to GCP console → Cloud Build → Triggers and update the settings as below.
Also, update the substitution variables (some of them will already be pre-populated).
Trigger a new build through a git push and you should see the build steps under the History tab.
That’s it, you have deployed your django app on Cloud Run!
Next steps
Once you have your base app working, the possible next steps can be:
- Expand the django app — add models, views, and other modules.
- Create separate triggers for dev and feature branches that deploy a different Cloud Run service.
- Connect the Cloud Run service to use other GCP services e.g. Pub/Sub
Thanks for reading!
(Full code available here)