Introduction

Configuration

This guide covers configuring your CellCMS project, including environment variables, asset storage, monitoring, and security best practices.

Environment Variables

Project Configuration

VariableRequiredDefaultDescription
JWT_SECRETYesJWT signing secret. Generate with openssl rand -base64 64
CORS_ORIGINNohttps://studio.cellcms.comAllowed CORS origin
STORAGE_TYPENos3Asset storage backend (s3)
S3_BUCKETNoS3/R2 bucket name
S3_REGIONNoS3 region
S3_ENDPOINTNoCustom S3 endpoint (R2)
S3_ACCESS_KEY_IDNoS3 access key
S3_SECRET_ACCESS_KEYNoS3 secret key

API Variables

VariableDefaultDescription
DATABASE_URLPostgreSQL connection string
JWT_SECRETJWT signing secret
JWT_ACCESS_EXPIRES_IN15mAccess token lifetime
JWT_REFRESH_EXPIRES_IN7dRefresh token lifetime
PORT4000API server port
HOST0.0.0.0API server bind address
CORS_ORIGINhttps://studio.cellcms.comAllowed CORS origin
STORAGE_TYPEs3Asset storage backend

PostgreSQL Configuration

Connection Pool

The API uses a connection pool with these defaults:

SettingValueDescription
max20Maximum connections
idleTimeoutMillis30,000Close idle connections after 30s
connectionTimeoutMillis5,000Fail 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:

PriorityListenerHost header(s)Target
20HTTPS:443studio.cellcms.comcellcms-studio-shared TG
21HTTPS:443api.cellcms.comcellcms-api-shared TG
22HTTPS:443cellcms.com, www.cellcms.comcellcms-website-shared TG
20HTTP:80all four CellCMS hostsredirect → 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:

TagValuePurpose
ProjectCellCMSDistinguish CellCMS spend from other projects sharing the account
StageprodSeparate prod from future dev/staging stacks
ManagedByCDKIndicates 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:

  1. AWS Console → Billing and Cost ManagementCost Allocation Tags
  2. Activate the user-defined tags Project, Stage, ManagedBy
  3. (Optional, for Fargate task-level granularity) activate AWS-generated tags aws:ecs:clusterName and aws:ecs:serviceName
  4. 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 with openssl rand -base64 64)
  • Change the default admin password
  • Set CORS_ORIGIN to 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:

  1. Stops accepting new connections
  2. Finishes in-flight requests
  3. Closes the database connection pool
  4. Exits cleanly