Configuration
This guide covers configuring your CellCMS project, including environment variables, asset storage, monitoring, and security best practices.
Environment Variables
Project Configuration
| Variable | Required | Default | Description |
|---|---|---|---|
JWT_SECRET | Yes | — | JWT signing secret. Generate with openssl rand -base64 64 |
CORS_ORIGIN | No | https://studio.cellcms.com | Allowed CORS origin |
STORAGE_TYPE | No | s3 | Asset storage backend (s3) |
S3_BUCKET | No | — | S3/R2 bucket name |
S3_REGION | No | — | S3 region |
S3_ENDPOINT | No | — | Custom S3 endpoint (R2) |
S3_ACCESS_KEY_ID | No | — | S3 access key |
S3_SECRET_ACCESS_KEY | No | — | S3 secret key |
API Variables
| Variable | Default | Description |
|---|---|---|
DATABASE_URL | — | PostgreSQL connection string |
JWT_SECRET | — | JWT signing secret |
JWT_ACCESS_EXPIRES_IN | 15m | Access token lifetime |
JWT_REFRESH_EXPIRES_IN | 7d | Refresh token lifetime |
PORT | 4000 | API server port |
HOST | 0.0.0.0 | API server bind address |
CORS_ORIGIN | https://studio.cellcms.com | Allowed CORS origin |
STORAGE_TYPE | s3 | Asset storage backend |
PostgreSQL Configuration
Connection Pool
The API uses a connection pool with these defaults:
| Setting | Value | Description |
|---|---|---|
max | 20 | Maximum connections |
idleTimeoutMillis | 30,000 | Close idle connections after 30s |
connectionTimeoutMillis | 5,000 | Fail if connection takes >5s |
For high-traffic projects, increase max by setting it in the DATABASE_URL or modifying the pool configuration.
Migrations
Migrations are applied automatically when your project is provisioned. For manual migration:
# Apply the initial schema
psql $DATABASE_URL < migrations/001_initial-schema.sql
# Or use the migration runner
pnpm migrate:up
To rollback:
pnpm migrate:down
Asset Storage
CellCMS stores assets in S3-compatible cloud storage.
S3 Configuration
STORAGE_TYPE=s3
S3_BUCKET=my-cellcms-assets
S3_REGION=us-east-1
S3_ACCESS_KEY_ID=AKIA...
S3_SECRET_ACCESS_KEY=...
Cloudflare R2:
STORAGE_TYPE=s3
S3_BUCKET=cellcms-assets
S3_ENDPOINT=https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com
S3_ACCESS_KEY_ID=...
S3_SECRET_ACCESS_KEY=...
Monitoring
Health Check
The /api/v1/health endpoint returns:
{
"status": "ok",
"timestamp": "2025-01-15T10:00:00.000Z",
"connections": 3
}
Use this for uptime monitoring and alerting.
Logging
CellCMS uses structured JSON logging (Pino) in production for easy integration with log aggregation services.
Shared ALB integration
CellCMS does not own its load balancer. It attaches to a shared ALB managed by BuildGuide's SharedInfra CDK stack, which also serves BuildGuide, SEOGEO, and Factorly. See the "Production deployment (AWS)" section in README.md for the high-level architecture and the BuildGuide repo's docs/shared-alb.md for the cross-project priority registry (the source of truth).
Listener-rule priority slice
CellCMS reserves priorities 20–29 on both the HTTP and HTTPS listeners. Currently used:
| Priority | Listener | Host header(s) | Target |
|---|---|---|---|
| 20 | HTTPS:443 | studio.cellcms.com | cellcms-studio-shared TG |
| 21 | HTTPS:443 | api.cellcms.com | cellcms-api-shared TG |
| 22 | HTTPS:443 | cellcms.com, www.cellcms.com | cellcms-website-shared TG |
| 20 | HTTP:80 | all four CellCMS hosts | redirect → HTTPS:443 |
23–29 are free for future CellCMS subdomains. Other projects own 10–19 (BuildGuide), 30–39 (SEOGEO), 40–49 (Factorly) — do not allocate outside the 20–29 slice without coordinating with BuildGuide first.
Preflight before cdk deploy
The CellCMS stack reads SSM parameters at deploy time. If any are missing, CFN fails with a ParameterNotFound error mid-deploy. Verify them all up front:
# Shared-ALB contract (created by BuildGuide's SharedInfra stack):
aws ssm get-parameter --name /shared/alb/security-group-id --region us-east-1 >/dev/null
aws ssm get-parameter --name /shared/alb/listener-https-arn --region us-east-1 >/dev/null
aws ssm get-parameter --name /shared/alb/listener-http-arn --region us-east-1 >/dev/null
aws ssm get-parameter --name /shared/alb/dns-name --region us-east-1 >/dev/null
# CellCMS-owned secrets (created manually with put-parameter --type SecureString):
aws ssm get-parameter --name /cellcms/jwt-secret --with-decryption --region us-east-1 >/dev/null
aws ssm get-parameter --name /cellcms/db-url --with-decryption --region us-east-1 >/dev/null
Each command should exit 0 silently. Any non-zero exit means deploy that parameter first.
-shared target-group suffix is load-bearing
The target groups in the stack are named cellcms-{studio,api,website}-shared. The suffix is a deliberate one-shot CFN sequencing trick: when the migration deploy ran, the un-suffixed cellcms-{studio,api,website} TGs were still attached to the retired cellcms-alb, and AWS forbids attaching one TG to two ALBs simultaneously. Renaming forced CFN to create fresh TGs on the shared ALB and detach the old ones cleanly.
Do not rename the suffix back to plain cellcms-{studio,api,website} in a "cleanup" deploy without re-deriving the consequences — at minimum, the old TG names may still be reserved on the deleted ALB's history, and re-using them is risky.
CI deploys
.github/workflows/deploy.yml only runs aws ecs update-service --force-new-deployment --desired-count 1. It does not touch the ALB, listener rules, or target groups. CDK deploys from this repo also preserve the shared-ALB wiring (the ALB is imported via SSM tokens, not via CFN cross-stack references — so it cannot be modified or deleted by this stack).
Cost Allocation Tags
All CellCMS AWS resources are tagged so spend can be isolated in AWS Cost Explorer:
| Tag | Value | Purpose |
|---|---|---|
Project | CellCMS | Distinguish CellCMS spend from other projects sharing the account |
Stage | prod | Separate prod from future dev/staging stacks |
ManagedBy | CDK | Indicates the resource is provisioned via the infra/ CDK stack |
CDK-managed resources
Tags are applied automatically via cdk.Tags.of(app).add(...) in infra/bin/cellcms.ts. They propagate to every resource the stack creates: ECS cluster + service + task definition, target groups + listener rules, security groups, CloudWatch log group, and the CloudFormation stack itself. (The shared ALB itself is owned by BuildGuide's SharedInfra stack — see "Shared ALB integration" above — so this stack does not tag the load balancer.)
The ECS service also has propagateTags: SERVICE and enableECSManagedTags: true so tags flow to running Fargate tasks (where the bulk of the compute bill is generated). Note: only newly-launched tasks pick up tags — existing tasks keep their previous state until replaced.
No action needed beyond cdk deploy.
Imported resources
The S3 assets bucket, ECR repos, ACM certificate, and SSM parameters are imported into the CDK stack rather than created by it, so CDK cannot tag them. Run the tagging script once after the initial deploy:
bash infra/scripts/tag-imported.sh
The script is idempotent — re-run it any time an imported resource is replaced (e.g., reissuing the ACM certificate).
The shared VPC is intentionally excluded — it belongs to other infrastructure.
Manual one-time activation in AWS Billing
User-defined tags do not appear in Cost Explorer until they are activated as Cost Allocation Tags. This step is manual, per-payer-account, and one-time:
- AWS Console → Billing and Cost Management → Cost Allocation Tags
- Activate the user-defined tags
Project,Stage,ManagedBy - (Optional, for Fargate task-level granularity) activate AWS-generated tags
aws:ecs:clusterNameandaws:ecs:serviceName - Wait up to 24 hours for tags to populate Cost Explorer
After activation, in Cost Explorer choose Group by → Tag → Project to see CellCMS isolated as its own line.
Optional: Resource Group view
For a single-pane view of all CellCMS resources, create a Resource Group in the AWS Resource Groups console with filter Project=CellCMS.
Verification
# All CellCMS-tagged resources in the account
aws resourcegroupstaggingapi get-resources \
--tag-filters Key=Project,Values=CellCMS \
--region us-east-1 \
--query 'ResourceTagMappingList[].ResourceARN' \
--output table
After cdk deploy this should list the ECS, ALB, log group, and security group resources. After running tag-imported.sh it also includes the S3 bucket, ECR repos, ACM cert, and SSM parameters.
Future: Bedrock cost attribution
CellCMS does not currently call Bedrock. When the first Bedrock-using feature is added, the implementation will create an AWS::Bedrock::ApplicationInferenceProfile in the CDK stack carrying these same tags — Bedrock token usage will then attribute to Project=CellCMS in Cost Explorer automatically. Prompt caching will be implemented in the same change to reduce per-request token cost.
Security Checklist
Before going live:
- Set a strong
JWT_SECRET(generated withopenssl rand -base64 64) - Change the default admin password
- Set
CORS_ORIGINto your actual Studio domain - Review API token permissions (use read-only for frontends)
- Set up database backups
- Use S3 storage for assets
Graceful Shutdown
The API server handles SIGTERM and SIGINT signals gracefully:
- Stops accepting new connections
- Finishes in-flight requests
- Closes the database connection pool
- Exits cleanly
Related Documentation
- Getting Started — Quick start guide
- Authentication — JWT and token configuration
- Assets & Images — Storage configuration
- Troubleshooting — Common issues