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-RoleIf 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¶
- Go to CloudFormation > Create stack
- Upload
deploy-role.yaml - Enter the CampusCore admin role ARN when prompted
- 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:
- ACM certificate request — Terraform requests an SSL certificate for
{env}.campuscoreai.com - DNS validation record — Terraform creates a CNAME validation record in Route53
- 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. - 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:
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