Skip to content

03 — Deployment

Scope: Per-client. The client deploys their IAM role, we set the final pipeline variable, then trigger the first deployment.

Admin Account ID: 774222656146 | Admin Role ARN: arn:aws:iam::774222656146:role/CampusCore-Admin-Role If these are outdated, get the current values from Admin Account Setup and update them here.


3a. Client Deploys the Deploy Role (Their Account)

Time: ~10 minutes

The client deploys a CloudFormation template in their AWS account that creates the IAM deploy role. We provide:

  • Template: infrastructure/deploy-roles/deploy-role.yaml
  • Input: Our admin role ARN (see banner above)

Via AWS Console

  1. Go to CloudFormation > Create stack
  2. Upload deploy-role.yaml
  3. Enter the CampusCore admin role ARN when prompted
  4. Acknowledge IAM capability and create

Via CLI

aws cloudformation deploy \
  --template-file infrastructure/deploy-roles/deploy-role.yaml \
  --stack-name CampusCore-Deploy-Role \
  --parameter-overrides CampusCoreAdminRoleArn=<campuscore-admin-role-arn> \
  --capabilities CAPABILITY_NAMED_IAM \
  --no-fail-on-empty-changeset \
  --profile <your-aws-profile>

Replace <campuscore-admin-role-arn> with the admin role ARN from the banner at the top of this page, and <your-aws-profile> with the AWS CLI profile for the client's account (e.g., troy-pilot). This is the same command clients follow in Deploy the CampusCore IAM Role - both include the one shared snippet, so it can't drift.

Get the Deploy Role ARN

After the stack completes, the client provides us the output:

aws cloudformation describe-stacks \
  --stack-name CampusCore-Deploy-Role \
  --query 'Stacks[0].Outputs[?OutputKey==`DeployRoleArn`].OutputValue' \
  --output text \
  --profile <client-profile>

Automatic Updates

The deploy role includes CloudFormation self-update permissions. After the initial manual deployment, the CI/CD pipeline automatically updates this stack on every deploy — any permission changes committed to deploy-role.yaml take effect without manual intervention.

IAM Permissions in the Deploy Role

The deploy role (CampusCore-Deploy-Role) grants permissions for:

  • ECS + ECR: Full access (clusters, services, task definitions, repositories)
  • RDS: Full access (instances, subnet groups, parameter groups)
  • S3: Full access (buckets for app storage)
  • SQS: Full access (queues for document processing)
  • EC2/VPC: Dedicated VPC provisioning (VPC, subnets, IGW, NAT, route tables, flow logs, VPC endpoints) + security group management
  • ELB: Full access (ALBs, target groups, listeners)
  • IAM: Create/manage roles and policies (for ECS task roles)
  • CloudWatch Logs: Full access (log groups, log streams)
  • SSM Parameter Store: Full access (runtime config)
  • KMS: Decrypt/Encrypt access (for SSM parameters)
  • Auto Scaling: Full access (ECS service auto-scaling)
  • CloudFormation: Self-update permissions (deploy role updates itself via pipeline)
  • ACM: Certificate management (request, describe, delete certificates for HTTPS)

The full policy is defined in infrastructure/deploy-roles/deploy-role.yaml. Clients can review the exact permissions before deploying.


3b. Set the Deploy Role ARN

Now that the client has deployed their role, set the final pipeline variable:

gh variable set AWS_ROLE_ARN --env howard --body "arn:aws:iam::CLIENT_ACCOUNT_ID:role/CampusCore-Deploy-Role"

All pipeline configuration from Step 02 is now complete.


3c. Trigger Deployment

# Option A: Push to deploy branch
git push origin main:deploy/howard

# Option B: Manual workflow dispatch
gh workflow run deploy-aws.yml -f client=howard

Monitor the deployment in GitHub Actions. The pipeline will: 1. Provision base infrastructure (VPC, RDS, ECS cluster, ECR, ALB, S3, SQS) 2. If CampusCore SSL is enabled: provision the {env}.campuscoreai.com subdomain and ACM certificate 3. Build and push Docker images 4. Deploy the ECS services (web + worker) 5. Wait for service stability

Expected deployment times

Scenario First deploy Subsequent deploys
Without SSL (ENABLE_CUSTOM_DOMAIN_WITH_SSL=false) ~15 minutes ~5 minutes
With SSL (ENABLE_CUSTOM_DOMAIN_WITH_SSL=true) 45-75 minutes ~5 minutes

The first deployment is slower because of one-time infrastructure provisioning (VPC, RDS, ECS cluster, ALB). When custom domain with SSL is enabled, the deployment takes significantly longer because:

  1. ACM certificate request — Terraform requests an SSL certificate for {env}.campuscoreai.com
  2. DNS validation record — Terraform creates a CNAME validation record in Route53
  3. Certificate validation wait — AWS must verify domain ownership by checking the DNS record. This step alone typically takes 30-45 minutes and can occasionally take longer. Terraform will show aws_acm_certificate_validation.campuscore[0]: Still creating... during this wait — this is normal.
  4. HTTPS listener creation — once the certificate is issued, Terraform creates the ALB HTTPS listener

This is a one-time cost. Once the certificate is issued, it auto-renews and subsequent deployments skip all of these steps.


3d. DNS Configuration

Time: ~5 minutes

After the first deployment completes, ask the client to create a CNAME record pointing to the auto-provisioned subdomain:

ai.university.edu  ->  howard.campuscoreai.com

HTTPS is already configured — no further SSL setup needed. If the client uses Cloudflare, they should set SSL mode to "Full" or "Full (strict)" since CampusCore serves HTTPS.


Security Defaults

All infrastructure is provisioned with SOC 2-compliant encryption enabled by default. No additional configuration is required.

Encryption at rest: - RDS PostgreSQL — storage encrypted with AWS-managed KMS key - S3 buckets — AES-256 server-side encryption (SSE-S3) - SQS queues — AWS-managed server-side encryption (SSE-SQS)

Encryption in transit: - Database connections use TLS (sslmode=require) in cloud environments - Django enforces HTTPS via SSL redirect, HSTS (1 year), and Secure cookie flags - ALB serves HTTPS when custom domain with SSL is enabled

Application-level encryption: - Connector credentials (OAuth tokens, API keys) are Fernet-encrypted before database storage

For full details, see Security & SOC 2 Compliance.


Next: 04 — Post-Deployment