๐Ÿš€ Galv Backend Infrastructure (AWS CDK)

June 13, 2025 ยท View on GitHub

This repository defines the infrastructure for the Galv backend using AWS CDK (Python). It provisions everything needed to run the Django application in production, including compute, storage, database, email, background tasks, and static frontend hosting.


โœ… Quick Start: How to Deploy

  1. Ensure required artifacts are available (optional):

    • The specified version of the backend image should be available on GitHub Container Registry (GHCR)
    • The specified version of the frontend should be tagged in the GitHub repository

    By default, both frontend and backend will use the :latest tag, which is expected to be available in typical deployments.

  2. Bootstrap the CDK (once per account/region):

cdk bootstrap
  1. Configure your deployment in cdk.json (see below)

  2. Deploy the infrastructure:

Make sure you're logged into AWS CLI. If your login has exprired you'll get a Python error about the region not being configured in env.

cdk deploy --all --context backendVersion=<backend-version> --context frontendVersion=<frontend-version> --profile <aws-profile-name>

If you just want to deploy the backend service, you can run:

cdk deploy <project-name>-BackendStack --context backendVersion=<backend-version> --profile <aws-profile-name>

It is highly recommended that you specify backendVersion and frontendVersion in the command to ensure existing stacks get updated. If you just use 'latest', stacks will not be updated if the image tag has not changed.

The deployment will:

  • Provision a Postgres database
  • Deploy the backend service behind an Application Load Balancer (ALB)
  • Create scheduled and on-demand ECS tasks
  • Run initial setup (migrate, create_superuser, loaddata)
  • Schedule validation polling every 5 minutes
  • Build and deploy the static frontend to S3 + CloudFront

Everything will be tagged with project-name = the project name for easy identification.


โš™๏ธ Configuration: cdk.json Context Keys

Configure your environment by editing cdk.json. The following table describes the key options:

KeyDescription
nameA short name used to prefix resources
backendVersionTag for backend Docker image (e.g. from GHCR)
frontendVersionTag for frontend Docker image (e.g. from GHCR)
projectNameTagUsed for tagging and grouping resources
isProductionControls retention policies and protections, debugging, ECS exec
removalProtectionOverrides retention policies and protections (e.g., deletion protection)
mailFromUserLocal part of the default email sender address
mailFromDomainDomain for the default email sender address
frontendEnvironmentKey-value pairs of environment variables to pass to the frontend container
backendEnvironmentKey-value pairs of environment variables to pass to the backend container
frontendSecretsNameName of the AWS Secrets Manager entry for frontend secrets
frontendSecretsKeysList of secret keys to inject into the frontend container
backendSecretsNameName of the AWS Secrets Manager entry for backend secrets
backendSecretsKeysList of secret keys to inject into the backend container
monitorIntervalMinutesHow often to run the validation monitor task (0 disables it)
frontendSubdomainSubdomain for the frontend (e.g., app)
backendSubdomainSubdomain for the backend (e.g., api)
domainNameDomain name for the application (e.g., example.com)
isRoute53DomainWhether the domain is managed by Route 53 (true/false)
enableContainerInsightsWhether to enable CloudWatch Container Insights for ECS tasks (true/false). Defaults to isProduction.
certificateArnARN of an existing ACM certificate when the domain is not in Route 53
smtpSecretNameName of the AWS Secrets Manager entry for SMTP credentials. Defaults to projectNameTag-smtp

Example cdk.json:

{
  "app": "python3 app.py",
  "context": {
    "name": "galv",
    "mailFromDomain": "mail.example.com",
    "mailFromUser": "galv-no-reply",
    "projectNameTag": "galv",
    "backendVersion": "latest",
    "isProduction": true,
    "frontendEnvironment": {},
    "backendEnvironment": {
      "DJANGO_SUPERUSER_USERNAME": "admin",
      "DJANGO_LOG_LEVEL": "INFO",
      "DJANGO_USER_ACTIVATION_OVERRIDE_ADDRESSES": "",
      "DJANGO_USER_ACTIVATION_TOKEN_EXPIRY_S": ""
    },
    "frontendSecretsName": "galv-frontend-secrets",
    "frontendSecretsKeys": [],
    "backendSecretsName": "galv-backend-secrets",
    "backendSecretsKeys": [],
    "monitorIntervalMinutes": 5
  }
}

๐Ÿ” Certificates, WAF & Logs

The stacks need TLS certificates for the frontend and backend domains. When isRoute53Domain is true the certificates are created automatically and validated using Route 53 DNS records. If the domain is external you must supply an existing certificate ARN via the certificateArn context key. See get_aws_custom_cert_instructions for the manual ACM steps and example --context certificateArn=arn:aws:acm:us-east-1:<account-id>:certificate/<uuid>.

A WAFv2 WebACL is attached to each load balancer and a dedicated S3 log bucket is created for ALB, flow log and WAF logs.


๐Ÿงฑ Infrastructure Overview

๐Ÿ’ก Core Services

ResourcePurpose
ECS FargateHosts Django app (via gunicorn)
ALBRoutes web traffic to backend
RDS (Postgres)Application database
S3Media + static file storage + frontend
CloudFrontDelivers the frontend globally
Secrets ManagerStores DB, SMTP, and app secrets
SES (SMTP)Sends email (via IAM-auth SMTP)

โ™ป๏ธ ECS Tasks

TaskPurposeTrigger
setup_db.shRuns migrations, creates superuser, loads fixturesOnce at deploy
validation_monitorScans and validates datasetsScheduled (default: 5 min)
check_statusVerifies DB/S3/SMTP/Django configOptional post-deploy task

๐Ÿ› ๏ธ Running and Debugging

โœ… Health Check

The Application Load Balancer (ALB) performs health checks at /health/. Ensure your Django app exposes this endpoint and it returns 200 OK to signal the container is healthy.

๐Ÿงฐ On-demand checks

Run this ECS task to verify services are properly connected:

python manage.py check_status

Supports --json for CI-friendly output. Checks include:

  • Django config/system integrity
  • Database connection
  • S3 connection (if enabled)
  • SMTP configuration and connectivity

๐Ÿ“ฆ Logs

All ECS tasks log to CloudWatch. Log groups are named by task (e.g., check-status, validation-monitor). Use the AWS Console or CLI to view logs.


๐Ÿ“„ AWS Glossary

Full NameShort NameDescription
Elastic Container ServiceECSOrchestrates container deployment and scaling
Fargate-Serverless compute engine for ECS tasks and services
Application Load BalancerALBRoutes web traffic to ECS services with health checks and load balancing
Relational Database ServiceRDSManages the Postgres database for the backend
Simple Storage ServiceS3Stores media files, static files, and frontend assets
CloudFront-Global CDN for serving frontend assets from S3
Secrets Manager-Securely stores credentials and sensitive data
Simple Email ServiceSESSends application emails via SMTP with IAM-auth
GitHub Container RegistryGHCRStores and serves the Docker image used by the backend

Development

Development happens on the develop branch. Please target pull requests to develop for review.

Once changes are approved, they will be merged into main. Releases are tagged from main.

Please ensure your changes are well-tested and documented before submitting a PR.


Deploying with GitHub Actions

Galv repositories can use GitHub Actions to automate deployments. These actions will invoke this CDK to deploy the infrastructure. Ensure your repository has the necessary secrets configured for deployment.

The IAM role is written to be simple and robust rather than minimizing permissions. This means that it has broader permissions than necessary, so its use should be limited and carefully monitored.

To set up the relevant IAM roles and identities, you'll need to:

  1. Create an IAM role with:
    • Trusted entity: Web Identity
    • Web identity provider: token.actions.githubusercontent.com
    • Audience: sts.amazonaws.com
    • GitHub organization: galv-team
    • Permissions from permissions.json
  2. Create an IAM user