Google Cloud Deployment Guide
UA Capstone Tours — Django Backend
Table of Contents
Overview
This project uses the following Google Cloud services:
- Cloud Run — hosts the Django backend as a containerized application
- Cloud SQL (PostgreSQL 16) — managed database with PostGIS extension
- Cloud Build — builds and pushes Docker images
- Container Registry — stores Docker images
- Secret Manager — (optional) secure storage for secrets
The backend is a Django REST API built with Python 3.12, served by Gunicorn, and connected to a PostgreSQL database via the Cloud SQL Proxy.
Deployment Process
Step 1 — Prerequisites
- Install the Google Cloud CLI
- Run
gcloud auth loginand sign in with your Google account - Set your project:
gcloud config set project campus-tour-backend
Step 2 — Enable Required Services
gcloud services enable run.googleapis.com cloudbuild.googleapis.com sqladmin.googleapis.com secretmanager.googleapis.com artifactregistry.googleapis.com
Step 3 — Create the Cloud SQL Instance
gcloud sql instances create ua-tour-db \
--database-version=POSTGRES_16 \
--tier=db-perf-optimized-N-2 \
--region=us-central1 \
--storage-size=20
Set the database password:
gcloud sql users set-password postgres --instance=ua-tour-db --password="YourPassword"
Connect and initialize the database (requires Cloud Shell or local psql):
gcloud sql connect ua-tour-db --user=postgres --quiet
Then inside the SQL prompt:
CREATE DATABASE ua_tour;
\c ua_tour
CREATE EXTENSION IF NOT EXISTS postgis;
\q
Step 4 — Configure settings.py
The following settings must use environment variables so they can be configured via Cloud Run without changing code:
SECRET_KEY = os.getenv("DJANGO_SECRET_KEY")
DEBUG = os.getenv("DJANGO_DEBUG", "False").lower() == "true"
ALLOWED_HOSTS = os.getenv("DJANGO_ALLOWED_HOSTS", "*").split(",")
CSRF_TRUSTED_ORIGINS = [x for x in os.getenv("DJANGO_CSRF_TRUSTED_ORIGINS", "").split(",") if x]
DATABASES = {
"default": {
"ENGINE": "django.contrib.gis.db.backends.postgis",
"NAME": os.getenv("POSTGRES_DB", "ua_tour"),
"USER": os.getenv("POSTGRES_USER", "postgres"),
"PASSWORD": os.getenv("POSTGRES_PASSWORD", ""),
"HOST": os.getenv("POSTGRES_HOST", "localhost"),
"PORT": os.getenv("POSTGRES_PORT", "5432"),
}
}
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
WhiteNoise must be added to MIDDLEWARE right after SecurityMiddleware:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
...
]
Step 5 — Configure the Dockerfile
The Dockerfile must use Gunicorn (not runserver), collect static files at build time, and run as a non-root user:
FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PORT=8080 \
DJANGO_SETTINGS_MODULE=config.settings
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
curl gdal-bin libgdal-dev libgeos-dev \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt /app/
RUN pip install --no-cache-dir -r requirements.txt
COPY . /app/
RUN python manage.py collectstatic --noinput
RUN addgroup --system appgroup && \
adduser --system --ingroup appgroup appuser && \
chown -R appuser:appgroup /app
USER appuser
EXPOSE 8080
CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8080", "--workers", "2", "--log-level", "info"]
Step 6 — Create the Environment Variables File
Create a file called env.yaml in the backend folder with the following contents. This avoids command-line escaping issues:
DJANGO_DEBUG: "False"
DJANGO_ALLOWED_HOSTS: "ua-capstone-backend-845958693022.us-central1.run.app,ua-capstone-backend-ual6ehsstq-uc.a.run.app"
DJANGO_SECRET_KEY: "generated-secret-key"
DJANGO_CSRF_TRUSTED_ORIGINS: "https://ua-capstone-backend-845958693022.us-central1.run.app,https://ua-capstone-backend-ual6ehsstq-uc.a.run.app"
POSTGRES_DB: "ua_tour"
POSTGRES_USER: "postgres"
POSTGRES_PASSWORD: "database-password"
POSTGRES_HOST: "/cloudsql/campus-tour-backend:us-central1:ua-tour-db"
POSTGRES_PORT: "5432"
⚠️ Never commit
env.yamlto version control. Add it to.gitignore.
To generate a secure secret key:
python -c "import secrets; print(secrets.token_urlsafe(50))"
Step 7 — Build and Push the Docker Image
From the backend folder:
gcloud builds submit --tag gcr.io/campus-tour-backend/ua-capstone-backend .
Step 8 — Deploy to Cloud Run
gcloud run deploy ua-capstone-backend \
--image gcr.io/campus-tour-backend/ua-capstone-backend \
--platform managed \
--region us-central1 \
--allow-unauthenticated \
--add-cloudsql-instances campus-tour-backend:us-central1:ua-tour-db \
--env-vars-file env.yaml
⚠️ Always use
gcloud run deploy(notgcloud run services update) when managing environment variables. Theupdatecommand drops any variables you don't explicitly include.
Step 9 — Run Migrations
Only needs to be run once, or whenever database models change:
gcloud run jobs create migrate-job \
--image gcr.io/campus-tour-backend/ua-capstone-backend \
--region us-central1 \
--set-cloudsql-instances campus-tour-backend:us-central1:ua-tour-db \
--env-vars-file env.yaml \
--command python --args manage.py,migrate,--noinput
gcloud run jobs execute migrate-job --region us-central1
Step 10 — Create Admin Superuser
Create a createuser.py file in the backend folder:
import django
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
django.setup()
from django.contrib.auth.models import User
if not User.objects.filter(username='admin').exists():
User.objects.create_superuser('admin', 'admin@example.com', 'YourPassword')
print('Superuser created')
else:
u = User.objects.get(username='admin')
u.set_password('YourPassword')
u.save()
print('Password updated')
Rebuild the image (so the script is included), then run:
gcloud run jobs create createuser-job \
--image gcr.io/campus-tour-backend/ua-capstone-backend \
--region us-central1 \
--set-cloudsql-instances campus-tour-backend:us-central1:ua-tour-db \
--env-vars-file env.yaml \
--command python --args "createuser.py"
gcloud run jobs execute createuser-job --region us-central1
Administrator Guide
Accessing the Admin Panel
The Django admin panel is available at:
https://ua-capstone-backend-845958693022.us-central1.run.app/admin/
Log in with the superuser credentials created in Step 10.
Redeploying After Code Changes
Any time code is changed, the image must be rebuilt and redeployed:
# 1. Rebuild the image
gcloud builds submit --tag gcr.io/campus-tour-backend/ua-capstone-backend .
# 2. Redeploy
gcloud run deploy ua-capstone-backend \
--image gcr.io/campus-tour-backend/ua-capstone-backend \
--platform managed \
--region us-central1 \
--allow-unauthenticated \
--add-cloudsql-instances campus-tour-backend:us-central1:ua-tour-db \
--env-vars-file env.yaml
Running Database Migrations
Only needed when Django models change:
gcloud run jobs execute migrate-job --region us-central1
If the job doesn't exist yet, create it first (see Step 9).
Updating Environment Variables
Edit env.yaml locally, then redeploy using the full deploy command in Step 8. Never use gcloud run services update to change env vars as it will drop all other variables.
Changing the Database Password
gcloud sql users set-password postgres --instance=ua-tour-db --password="NewPassword"
Then update env.yaml with the new password and redeploy.
Viewing Logs
gcloud logging read "resource.type=cloud_run_revision AND resource.labels.service_name=ua-capstone-backend" \
--project=campus-tour-backend \
--limit=50 \
--format="value(textPayload)"
Checking Current Environment Variables
gcloud run services describe ua-capstone-backend \
--region us-central1 \
--format="value(spec.template.spec.containers[0].env)"
Checking Service Status
gcloud run services describe ua-capstone-backend --region us-central1
Common Commands Reference
| Task | Command |
|---|---|
| Set active project | gcloud config set project campus-tour-backend |
| Build and push image | gcloud builds submit --tag gcr.io/campus-tour-backend/ua-capstone-backend . |
| Deploy to Cloud Run | gcloud run deploy ... --env-vars-file env.yaml |
| Run migrations | gcloud run jobs execute migrate-job --region us-central1 |
| View logs | gcloud logging read "resource.type=cloud_run_revision ..." |
| Connect to database | gcloud sql connect ua-tour-db --user=postgres --quiet (requires Cloud Shell) |
| List revisions | gcloud run revisions list --service=ua-capstone-backend --region=us-central1 |
Troubleshooting
Bad Request (400)
ALLOWED_HOSTSdoes not include the current URL. Updateenv.yamland redeploy.- Check current value:
gcloud run services describe ... --format="value(spec.template.spec.containers[0].env)"
Internal Server Error (500)
- Usually means missing environment variables (especially
DJANGO_SECRET_KEY) or a database connection failure. - Check logs for the specific Python traceback.
DisallowedHost Error
- The request URL is not in
ALLOWED_HOSTS. Add the URL toenv.yamland redeploy.
CSRF / 400 on POST Requests
- The request origin is not in
CSRF_TRUSTED_ORIGINS. Add the fullhttps://URL toenv.yamland redeploy.
Static Files Not Loading (Admin Page Looks Bare)
- WhiteNoise is not configured, or
collectstaticwas not run during the Docker build. - Ensure
whitenoise.middleware.WhiteNoiseMiddlewareis inMIDDLEWAREandRUN python manage.py collectstatic --noinputis in the Dockerfile.
Env Vars Dropped After Update
- This happens when using
gcloud run services updateinstead ofgcloud run deploy. - Always use the full
gcloud run deploycommand with--env-vars-file env.yaml.
Cloud SQL Proxy Not Found (Local)
- Use Google Cloud Shell instead of local terminal for any commands requiring the Cloud SQL Proxy.
- Cloud Shell has all required tools pre-installed.