Skip to main content
CMDashCybersecurity Maturity Dashboard
DocsDevelopersSelf-hosting CMDash in your own AWS account

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.

TemplateUse it forWhat it creates
cmdash-selfhosted-eval.yamlAn evaluation or a private pilotA 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.yamlProductionA 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.yamlAWS 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 SshCidr and KeyName.
  • 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

ParameterDefaultMeaning
InstanceTypet3.larget3.large, t3.xlarge, m6i.large or m6i.xlarge
DataVolumeSizeGb50Encrypted gp3 data volume (20 to 16384): Postgres, Redis, licence, edge state
KmsKeyArnblankOptional customer-managed KMS key for the volumes and both buckets; blank uses AWS managed keys
AllowedIngressCidr0.0.0.0/0Who may reach ports 80 and 443
DomainNameblankOptional DNS name you point at the Elastic IP; blank serves the app on the IP
SshCidr, KeyNameblankOptional SSH access; set both or neither
VpcCidr10.80.0.0/16CIDR of the new VPC (/16 to /24)
BackupRetentionDays30Days the daily backups are kept
EmailAllowUnconfiguredfalsefalse makes every e-mail send fail loudly until mail is configured; true runs without e-mail on purpose
Licensinglicense-filelicense-file or marketplace (see below)

Production template

ParameterDefaultMeaning
DomainNamerequiredThe name users browse to
HostedZoneIdblankA public Route 53 zone containing DomainName: the stack requests and validates the ACM certificate and creates the alias record
CertificateArnblankAn issued ACM certificate in this region, used when HostedZoneId is blank. Set one of the two
InstanceTypem6i.larget3.large up to m6i.2xlarge
DataVolumeSizeGb30Redis, licence, edge state and backup staging
KmsKeyArnblankOptional customer-managed key for EBS, RDS, both buckets and both secrets
DbInstanceClassdb.t3.mediumdb.t3.medium up to db.r6i.large
DbEngineVersion16.13PostgreSQL 16 minor version
DbAllocatedStorageGb50RDS storage (gp3, encrypted)
DbMultiAztrueA standby in a second Availability Zone
DbDeletionProtectionfalseTurn on once the install holds real data
VpcCidr10.80.0.0/16CIDR of the new VPC (/16 to /22)
BackupRetentionDays30Days the daily logical backups are kept; RDS automated backups are kept 7 days
EmailAllowUnconfiguredfalseAs above
Licensinglicense-fileAs 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

  1. 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.
  2. 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/ready answers 200, which requires Postgres, Redis and the S3 bucket to answer.
  3. Read the stack outputs. They include Url, SsmSessionCommand, FirstBootLogCommand, DataVolumeId, ObjectsBucketName and BackupsBucketName; the production template also outputs LoadBalancerDnsName, DatabaseEndpoint and DatabaseSecretArn (the ARN, never a value).
  4. Production only: if you did not pass HostedZoneId, point DomainName at LoadBalancerDnsName. Eval: if you set DomainName, point its A record at the PublicIp output.

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:

  1. 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).
  2. Create SES SMTP credentials (SES console, SMTP settings). These are not your IAM access keys: the SMTP password is derived from them by SES.
  3. Open a session (SsmSessionCommand output) and add to /opt/cmdash/.env:
    code
    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=false
    
    then restart the api: cd /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 in CMDASH_COMPOSE_FILES in .env, the same list first boot used).
  4. 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 Licensing set to license-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 in docs/cmdash/05-licensing.md section 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 with pg_restore --list before 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:

KeptDeleted
A final snapshot of the data volumeThe instance and its root volume
Production and GovCloud: a final RDS snapshot, and RDS automated backups until their retention endsThe 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.

CallWhenWhy
AWS License Manager CheckoutLicense and GetLicense, through the instance roleOnly when Licensing is marketplace: at boot, hourly and daily at 02:00Confirm 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_URLOnly on licence-file installs where CONTROL_PLANE_URL is set, once a day. The templates do not set itA 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-releasesWhen the Updates page looks for releasesList published CMDash releases
ghcr.io/bgovanlu/cmdashWhen you apply an in-app updatePull the release's images
Amazon S3, with the instance roleAlwaysObjects and exports, and the daily backup upload
AWS Secrets ManagerProduction and GovCloud, at first bootRead the database credentials and the application master secret
Amazon EC2During an AMI updateStop 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).

  • 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.

This page is the product manual, published from the repository at docs/developers/self-host-aws.md. Customers read the same text inside CMDash under Help. Last updated October 12, 2026.

Self-hosting CMDash in your own AWS account — CMDash Docs