Self-hosting CMDash in your own AWS account
CMDash runs in your own AWS account from one CloudFormation template and one CMDash AMI. The template creates the network, the instance, the data volume, the S3 buckets and (on the production template) the load balancer and the database; the AMI carries the images, compose files and edge configuration, so nothing is downloaded when the instance first boots. The same code runs as in the hosted service.
This page covers the AWS install. For a Docker host you manage yourself, read
self-host.md. The two templates' internals (IAM, first-boot
contract, restore commands) are in docs/cmdash/04-byo-aws-and-provisioning.md,
and the licensing modes are in docs/cmdash/05-licensing.md; this page links to
them instead of repeating them.
Pick a template
All templates live in deploy/aws/. Each is a single stack (no nested
templates) and launches the CMDash AMI from its AmiByRegion mapping.
| Template | Use it for | What it creates |
|---|---|---|
cmdash-selfhosted-eval.yaml | An evaluation or a private pilot | A new VPC with one public subnet; one instance with an Elastic IP; Postgres and Redis in containers on the data volume; Caddy with a self-signed certificate on 80 and 443 |
cmdash-selfhosted-prod.yaml | Production | A new VPC with public and private subnets; an internet-facing Application Load Balancer with an ACM certificate; the instance in a private subnet with no public address; Amazon RDS for PostgreSQL 16 (encrypted, Multi-AZ by default) |
cmdash-selfhosted-govcloud.yaml | AWS GovCloud (US) | The production shape with partition-aware ARNs, FIPS endpoints for S3 and the AWS CLI, a FIPS TLS policy on the load balancer, and a US-regions-only rule |
The eval and production templates are published for us-east-1, us-east-2,
us-west-1 and us-west-2. The GovCloud template additionally accepts
us-gov-west-1 and us-gov-east-1. cmdash-byo-aws.yaml is retired and cannot
complete a new launch.
Every template provides:
- IMDSv2 only. Only the api container can reach instance metadata.
- No SSH by default. Access is AWS Systems Manager Session Manager. The eval
template opens port 22 only when you set both
SshCidrandKeyName. - Internet access on ports 80 and 443 only. Port 80 redirects to HTTPS.
- Two S3 buckets per stack (objects and backups): versioned, encrypted, public access blocked, TLS only, retained when the stack is deleted.
- A separate encrypted data volume that outlives the instance.
- Secrets that never appear in user-data, stack outputs or logs.
Parameters
Eval template
| Parameter | Default | Meaning |
|---|---|---|
InstanceType | t3.large | t3.large, t3.xlarge, m6i.large or m6i.xlarge |
DataVolumeSizeGb | 50 | Encrypted gp3 data volume (20 to 16384): Postgres, Redis, licence, edge state |
KmsKeyArn | blank | Optional customer-managed KMS key for the volumes and both buckets; blank uses AWS managed keys |
AllowedIngressCidr | 0.0.0.0/0 | Who may reach ports 80 and 443 |
DomainName | blank | Optional DNS name you point at the Elastic IP; blank serves the app on the IP |
SshCidr, KeyName | blank | Optional SSH access; set both or neither |
VpcCidr | 10.80.0.0/16 | CIDR of the new VPC (/16 to /24) |
BackupRetentionDays | 30 | Days the daily backups are kept |
EmailAllowUnconfigured | false | false makes every e-mail send fail loudly until mail is configured; true runs without e-mail on purpose |
Licensing | license-file | license-file or marketplace (see below) |
Production template
| Parameter | Default | Meaning |
|---|---|---|
DomainName | required | The name users browse to |
HostedZoneId | blank | A public Route 53 zone containing DomainName: the stack requests and validates the ACM certificate and creates the alias record |
CertificateArn | blank | An issued ACM certificate in this region, used when HostedZoneId is blank. Set one of the two |
InstanceType | m6i.large | t3.large up to m6i.2xlarge |
DataVolumeSizeGb | 30 | Redis, licence, edge state and backup staging |
KmsKeyArn | blank | Optional customer-managed key for EBS, RDS, both buckets and both secrets |
DbInstanceClass | db.t3.medium | db.t3.medium up to db.r6i.large |
DbEngineVersion | 16.13 | PostgreSQL 16 minor version |
DbAllocatedStorageGb | 50 | RDS storage (gp3, encrypted) |
DbMultiAz | true | A standby in a second Availability Zone |
DbDeletionProtection | false | Turn on once the install holds real data |
VpcCidr | 10.80.0.0/16 | CIDR of the new VPC (/16 to /22) |
BackupRetentionDays | 30 | Days the daily logical backups are kept; RDS automated backups are kept 7 days |
EmailAllowUnconfigured | false | As above |
Licensing | license-file | As above |
The GovCloud template takes the production parameters except Licensing
(it uses the licence file), plus UseFipsEndpoints (default true).
Licensing
Licensing chooses where the install reads its plan from. The choice is made
once, at launch, and the app reads exactly one source.
license-file: a signed CMDash licence file that you upload in the app. The install makes no AWS License Manager call.marketplace: the plan comes from your AWS Marketplace subscription. The api checks the entitlement with AWS License Manager through the instance role, at boot, hourly and in the daily 02:00 re-check, and a licence file is neither read nor accepted.
The full table of sources, grace periods and the pinned product id is in
docs/cmdash/05-licensing.md section 1.1.
Launch
- In the CloudFormation console (or with
aws cloudformation create-stack --capabilities CAPABILITY_IAM), create a stack from the template for your shape and fill in the parameters above. - Wait for
CREATE_COMPLETE. The instance's first boot starts the stack from the AMI's images and reports its result to CloudFormation. The stack succeeds only when/api/health/readyanswers 200, which requires Postgres, Redis and the S3 bucket to answer. - Read the stack outputs. They include
Url,SsmSessionCommand,FirstBootLogCommand,DataVolumeId,ObjectsBucketNameandBackupsBucketName; the production template also outputsLoadBalancerDnsName,DatabaseEndpointandDatabaseSecretArn(the ARN, never a value). - Production only: if you did not pass
HostedZoneId, pointDomainNameatLoadBalancerDnsName. Eval: if you setDomainName, point its A record at thePublicIpoutput.
The eval template serves a self-signed certificate, so the browser shows a warning on the first visit. The production template serves your ACM certificate.
First sign-in
Open the Url output and register the first account on the sign-up page. On an
install that holds no organization yet and has no mail transport configured, the
first sign-up creates the organization at once, and that account is its owner. If
you configure mail, the sign-up link is e-mailed instead. Configuration
variables, including mail, are listed in deploy/compose/README.md.
Mail on the AWS stack
Without a mail transport the first owner still works (see above), but invitations, password resets and later sign-ups cannot send. To use Amazon SES:
- In SES (the stack's region), verify a sending identity and, for a new account, request production access (the sandbox only delivers to verified addresses).
- Create SES SMTP credentials (SES console, SMTP settings). These are not your IAM access keys: the SMTP password is derived from them by SES.
- Open a session (
SsmSessionCommandoutput) and add to/opt/cmdash/.env:then restart the api:SMTP_HOST=email-smtp.<region>.amazonaws.com SMTP_PORT=587 SMTP_USER=<SES SMTP user name> SMTP_PASSWORD=<SES SMTP password> SMTP_FROM=noreply@<your verified domain> EMAIL_ALLOW_UNCONFIGURED=falsecd /opt/cmdash && docker compose --project-name cmdash --env-file .env -f docker-compose.yml -f docker-compose.onprem.yml up -d api(add the AWS override files named inCMDASH_COMPOSE_FILESin.env, the same list first boot used). - Send yourself a password reset to confirm.
/opt/cmdash/.env lives on the instance's root volume. First boot rewrites it when a
replacement instance takes over the data volume, so keep a copy of these lines (a password
manager, not the data volume's secrets/ directory) and re-apply them after a replacement.
Then open Settings, License:
- Licence file installs show the licence state, tier and expiry, and offer a licence upload.
- AWS Marketplace installs show the subscription state, when AWS last
confirmed it, and offer no upload or download. For about a minute after a boot
the page reads "Checking the AWS Marketplace subscription" and writes are
blocked until AWS answers; reload to see the result. If the page reads "This
CMDash release does not support AWS Marketplace licensing", the release the
install runs accepts no Marketplace product id; contact CMDash support for a
release that does, or relaunch with
Licensingset tolicense-file. A subscription that AWS no longer confirms moves the install to a grace period and then to read-only, with reading and export unaffected. The states, timings and wording are described indocs/cmdash/05-licensing.mdsection 1.1.
Backups
cmdash-backup-s3.timer runs daily at 03:15 (with up to 15 minutes of jitter,
and a catch-up run after downtime). Each run writes two objects to the backups
bucket:
postgres/<UTC timestamp>.dump:pg_dump --format=custom, proven restorable withpg_restore --listbefore it is uploaded;license/<UTC timestamp>.tar.gz: the licence directory.
The bucket's lifecycle rule expires them after BackupRetentionDays. The
production and GovCloud templates also keep RDS automated backups for 7 days and
take a final snapshot when the stack is deleted.
Two things are not in the backup bucket, on purpose. The generated secrets: on
the eval template copy /var/lib/cmdash-data/secrets/app-secrets.env somewhere
safe yourself, because a restored database cannot decrypt its stored integration
credentials without its CREDENTIAL_ENCRYPTION_KEY; on production they are in
Secrets Manager. And the proof-log ledger, which a database restore never rolls
back.
If a backup fails, the unit fails and the reason is in
journalctl -u cmdash-backup-s3. The restore procedure, for both shapes, is in
docs/cmdash/04-byo-aws-and-provisioning.md under "Backups and restore".
Upgrades
There are two paths.
In the app (Settings, Updates). An owner or administrator selects a release
and applies it. The updater service rolls api and then web on the instance
and rolls back to the previous release if the health gate fails. The Updates
page reads the published release list from api.github.com
(bgovanlu/cmdash-releases), and applying an update pulls the release's images
from ghcr.io/bgovanlu/cmdash. A release whose notes mark a database schema
change is not offered by the in-app updater; the Updates page states why.
A new AMI (a stack update). Update the stack to a template version whose
AmiByRegion mapping carries the new AMI. CloudFormation replaces the instance.
The new instance's first boot stops the old one, takes over the data volume,
applies the schema without --accept-data-loss, and starts the new release. If
that first boot fails, it hands the volume back and restarts the old instance, and
the stack finishes in UPDATE_ROLLBACK_COMPLETE with the old instance running.
A schema change that db push refuses fails closed rather than dropping data;
docs/cmdash/04-byo-aws-and-provisioning.md ("Schema changes that db push
refuses") describes the recovery.
Uninstall
Delete the CloudFormation stack. What happens to your data:
| Kept | Deleted |
|---|---|
| A final snapshot of the data volume | The instance and its root volume |
| Production and GovCloud: a final RDS snapshot, and RDS automated backups until their retention ends | The VPC, load balancer, NAT gateway, security groups and Elastic IP |
| Both S3 buckets (objects and backups) | The IAM role and instance profile |
| The Secrets Manager secrets, after their recovery window |
Copy the secrets first if you intend to restore the RDS snapshot elsewhere: the application master secret derives the credential-encryption key. Empty and delete the retained buckets and snapshots yourself when you are sure you no longer need them.
Outbound calls
These are the outbound calls the install makes with its default configuration. Features you switch on yourself (AI, integrations, webhooks) make the calls their own settings describe.
| Call | When | Why |
|---|---|---|
AWS License Manager CheckoutLicense and GetLicense, through the instance role | Only when Licensing is marketplace: at boot, hourly and daily at 02:00 | Confirm the AWS Marketplace subscription and read its tier. A licence-file install makes no License Manager call |
The signed licence revocation list, from CONTROL_PLANE_URL | Only on licence-file installs where CONTROL_PLANE_URL is set, once a day. The templates do not set it | A verified list that names your licence moves the install toward read-only. An unreachable or unverifiable answer changes nothing |
api.github.com, repository bgovanlu/cmdash-releases | When the Updates page looks for releases | List published CMDash releases |
ghcr.io/bgovanlu/cmdash | When you apply an in-app update | Pull the release's images |
| Amazon S3, with the instance role | Always | Objects and exports, and the daily backup upload |
| AWS Secrets Manager | Production and GovCloud, at first boot | Read the database credentials and the application master secret |
| Amazon EC2 | During an AMI update | Stop and start this stack's instances and move this stack's data volume; the permissions are scoped to resources created by the stack |
The threat-intelligence feed refreshes (CISA KEV and EPSS) are off on AWS installs
unless you opt in with KEV_FEED_ENABLED and EPSS_FEED_ENABLED. Presigned download links end when the
instance role's temporary credentials expire (about six hours).
Related
- self-host.md: the Docker host install.
docs/cmdash/04-byo-aws-and-provisioning.md: IAM, first-boot contract, restore, rollback.docs/cmdash/05-licensing.md: licensing sources and the Marketplace licence check.docs/marketplace/: the two AWS Marketplace listings, with usage instructions and architecture diagrams.