Deployment guide

Community Health Toolkit 5.3.3

Overview

The Community Health Toolkit (CHT) is the open source platform, stewarded by Medic, for building digital health apps used by community health workers and the health facilities that support them. Apps cover registering households and people, guided visits and assessments, tasks and follow-ups, targets, SMS workflows and offline-first mobile use. CHT Core is the server those apps run on.

This guide covers launching and using the Community Health Toolkit 5.3.3 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. The image gives you a working CHT Core server with Medic's default configuration. You then deploy your own project configuration to it with cht-conf.

The image is one version in the SecureAnyCloud Hardened EHRbase & GIS, Health and Research Platforms AMIs listing on AWS Marketplace. Choose it as the software version when you subscribe and launch.

What you get:

  • CHT Core 5.3.3 from Medic's six official container images, unmodified and each pinned by digest: cht-nginx, cht-api, cht-sentinel, cht-haproxy, cht-couchdb (CouchDB, single node) and cht-couchdb-nouveau (search). They start from Medic's own Docker Compose files for 5.3.3 plus one SecureAnyCloud override
  • Every image already on the disk, so first boot and every later boot start the stack without pulling anything
  • Docker CE from Docker's apt repository, with its signing key pinned by fingerprint, running the stack under the systemd unit community-health-toolkit.service
  • HTTPS on port 443 from cht-nginx with a certificate made for this instance on first boot, and port 80 redirecting to HTTPS
  • A per-instance CouchDB administrator password (the medic account, which is also the CHT administrator), CouchDB cookie secret, CouchDB UUID and Erlang distribution cookie, all generated on first boot
  • cht-set-hostname, a helper that serves CHT under your own DNS name and re-issues the certificate for it

Default user: ubuntu. Architecture: x86_64. Root volume: 20 GiB gp3.

Security posture

  • No default login. Medic's example configuration uses a fixed CouchDB password, and the upstream CouchDB image ships a fixed Erlang cookie. Neither is on this image. Before any container starts, first boot generates a 32-character administrator password, a 64-character cookie secret, a UUID and a 48-character Erlang cookie on each instance, so no two instances share one. The Erlang cookie is read from a file only CouchDB's account can read, never from a command line. medic/password, admin/admin and the other well-known logins are refused.
  • No data from the build. The CouchDB data directory is empty on the image and created on first boot. There is no build-time user, no demo data and no known password hash.
  • Only cht-nginx faces the network. Docker publishes ports 80 and 443 and nothing else. CouchDB (5984), haproxy, the CHT API (5988), the search service and the Erlang ports sit on the internal cht-net network with no host port.
  • The certificate is made on the instance. First boot creates a self-signed end-entity certificate (CA:FALSE, server authentication only) for the instance's addresses. It is never baked into the image. You can replace it with your own at any time (Step 8).
  • The hostname comes from configuration, never from a request. nginx answers only for the configured name and the instance's addresses. Any other Host header or TLS name gets its connection closed. nginx hands CHT that fixed name and replaces any client-supplied X-Forwarded-For, X-Real-IP or Forwarded header with the real peer address. CHT's sign-in limiter therefore counts each client separately, and a forged header cannot move it.
  • Two upstream library advisories are blocked at the edge. CHT 5.3.3 ships two Node.js libraries with published HIGH advisories and no fixed CHT release yet, and nginx closes each one without changing Medic's images:
  • body-parser, CVE-2026-111181: a request body sent with a Content-Encoding (gzip, deflate, br) can crash the CHT API before it checks who is signed in. nginx answers 415 Unsupported Media Type to any request whose body has a Content-Encoding other than identity, and never passes that header to the API.
  • compression, CVE-2026-87776: a client that drops a compressed response early can make the API leak memory. nginx always reads the API's response to its end (proxy_ignore_client_abort on), so the API never sees a dropped connection.
  • What this means for your clients: the CHT web app and the CHT Android app never compress what they send, so sign-in and offline sync are unaffected. Validation on this image proves both: a browser-style sign-in, and an offline health worker's full sync (download, upload and the metadata database) using CHT's own sync code. Every request header was recorded, and none carried a Content-Encoding.
  • Only a custom integration that compresses its request bodies gets 415. Have it send them uncompressed. SecureAnyCloud drops both controls in the release that moves to a CHT version with fixed libraries.
  • The containers cannot reach your private network or instance metadata. CHT can call URLs you configure (outbound push, SMS gateways). A firewall chain on the stack's Docker bridge rejects every new connection from the containers to link-local addresses (including the metadata service), carrier-grade NAT, RFC 1918 ranges, your VPC's CIDR blocks, loopback and reserved ranges, and the instance itself. Only DNS to the instance's resolver and the public internet are allowed.
  • The containers have no IPv6. The firewall rules are in place before Docker starts any container. Docker, containerd and the CHT units are also denied metadata by systemd IPAddressDeny.
  • No core dumps. The containers and the CHT units run with core dumps disabled.
  • IMDSv2 required. The AMI is registered with ImdsSupport v2.0, so instances refuse token-less (IMDSv1) metadata requests.
  • Per-instance identity. SSH host keys, the machine ID, the TLS certificate and Docker's engine ID are generated fresh on every instance.

Medic's containers receive the CouchDB credentials as environment variables, as upstream designed them, so sudo docker inspect shows them to root. Nothing else on the instance exposes them. The files that hold them are root-only, and the Docker group is empty.

Prerequisites

  • An AWS account and permission to launch EC2 instances
  • An EC2 key pair in the Region you launch in
  • A computer with cht-conf (Node.js) if you will deploy your own CHT configuration

Recommended instance type: m5.large (2 vCPUs, 8 GiB). With the stack running, a signed-in administrator and the guide's sample data, the instance uses about 1.6 GiB of its 8 GiB (CouchDB about 560 MiB, the search service about 270 MiB, the API about 200 MiB).

Medic's guidance for production is 4 vCPUs, 8 GiB and 100 GB of SSD. Grow the root volume as your data grows.

Connecting to your instance

Version Operating system SSH user

Community Health Toolkit 5.3.3 on Ubuntu 24.04 Ubuntu 24.04 LTS ubuntu

1

Launch the instance

Option A: AWS Marketplace console

  • Open the SecureAnyCloud Hardened EHRbase & GIS, Health and Research Platforms AMIs listing on AWS Marketplace and choose View purchase options, then Subscribe. One subscription covers every version in the listing.
  • Once the subscription is active, choose Launch your software.
  • Choose One-click launch from AWS Marketplace (or Launch from EC2 Console for the full launch wizard). In Version, choose Community Health Toolkit 5.3.3 on Ubuntu 24.04, then choose your Region.
  • Pick an instance type (m5.large recommended; the software charge is $0.04 per vCPU per hour) and your key pair.
  • Set the security group. The listing's default group opens SSH (22), HTTP (80) and HTTPS (443). Restrict SSH to your own IP address, and restrict 80 and 443 to the networks your health workers and administrators use.
  • Choose your VPC and subnet and leave the metadata defaults, because the image already requires IMDSv2. Then choose Launch.

Option B: AWS CLI

Run the following on your own workstation. It creates a security group that allows SSH from your management address and HTTPS from your own network, then launches the instance.

Replace the AMI ID with the one shown for your Region on the Marketplace launch page. Replace the VPC, subnet, key pair and CIDR ranges with your own:

SG=$(aws ec2 create-security-group --group-name community-health-toolkit \
  --description "Community Health Toolkit" --vpc-id vpc-0123456789abcdef0 \
  --query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 22 --cidr <your-mgmt-cidr>
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 443 --cidr 203.0.113.0/24
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 80 --cidr 203.0.113.0/24
aws ec2 run-instances \
  --image-id ami-0123456789abcdef0 \
  --instance-type m5.large \
  --key-name my-key-pair \
  --security-group-ids "$SG" \
  --subnet-id subnet-0123456789abcdef0 \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=community-health-toolkit}]'

You do not need --metadata-options, because the AMI already requires IMDSv2. To keep the address across a stop and start, associate an Elastic IP.

2

Connect and find your login

Connect as ubuntu with the key pair you chose:

ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip>

First boot takes about a minute. It generates the secrets and sets the hostname CHT is served as, which is the instance's public IP address until you choose a name. It creates the TLS certificate for that name and starts the stack. It then proves that the administrator password signs in and that the well-known default logins do not.

First boot also writes /root/community-health-toolkit-credentials.txt (mode 0600, root). The following commands list the entries in that file, then the names of every key it holds. The password itself is not shown:

sudo grep -E '^CHT_(URL|ADMIN_USER)=' /root/community-health-toolkit-credentials.txt
sudo grep -oE '^[A-Z0-9_]+=' /root/community-health-toolkit-credentials.txt | tr -d '='
sudo stat -c '%a %U:%G' /root/community-health-toolkit-credentials.txt
CHT_URL=https://<public-ip>/ CHT_ADMIN_USER=medic CHT_URL CHT_ADMIN_USER CHT_ADMIN_PASSWORD CHT_TLS_SHA256_FINGERPRINT 600 root:root

Value What it is for

CHT_URL The address of this instance's CHT web app

CHT_ADMIN_USER medic, the CouchDB server administrator and CHT administrator

CHT_ADMIN_PASSWORD Its password, generated on this instance

CHT_TLS_SHA256_FINGERPRINT The SHA-256 fingerprint of this instance's certificate, to check the browser warning against

To read the password, run sudo cat /root/community-health-toolkit-credentials.txt in your own session. Store the password in your password manager.

3

Confirm CHT is running

Check the stack with the following commands. The firewall units, Docker and the CHT stack are active, and the six containers run from Medic's pinned images. Only cht-nginx has host ports, and only sshd and ports 80 and 443 listen beyond loopback. The monitoring API reports the versions:

systemctl is-active docker.service sac-cht-vpc-cidrs.service sac-docker-imds-deny.service community-health-toolkit.service
sudo docker ps --format '{{.Names}} {{.Image}} {{.Ports}}' | sed -E 's/@sha256:[0-9a-f]+//' | sort
sudo ss -Htln | awk '{print $4}' | grep -vE '^(127\.|\[::1\])' | sort -u curl -sk https://127.0.0.1/api/v2/monitoring | jq -c '{app: .version.app, couchdb: .version.couchdb}'
active active active active cht-api-1 public.ecr.aws/medic/cht-api:5.3.3 5988/tcp cht-couchdb-1 public.ecr.aws/medic/cht-couchdb:5.3.3 4369/tcp, 5984/tcp, 9100/tcp cht-haproxy-1 public.ecr.aws/medic/cht-haproxy:5.3.3 5984/tcp cht-nginx-1 public.ecr.aws/medic/cht-nginx:5.3.3 0.0.0.0:80->80/tcp, [::]:80->80/tcp, 0.0.0.0:443->443/tcp, [::]:443->443/tcp cht-nouveau-1 public.ecr.aws/medic/cht-couchdb-nouveau:5.3.3 5987-5988/tcp cht-sentinel-1 public.ecr.aws/medic/cht-sentinel:5.3.3 0.0.0.0:22 0.0.0.0:443 0.0.0.0:80 [::]:22 [::]:443 [::]:80 {"app":"5.3.3","couchdb":"3.5.3"}
4

Prove the login and the defaults

Run the following checks. Signed out, the databases and the admin API are refused, and so are the well-known default logins. Plain HTTP redirects to HTTPS, and an unknown Host header gets no answer.

The last command runs the image's round-trip check. It signs in as the administrator, creates a health facility and a health worker, and signs in as that worker. It then deletes both and confirms that the deleted worker is refused:

B=https://127.0.0.1 curl -sk -o /dev/null -w '_all_dbs, signed out -> %{http_code}\n' -H 'Accept: application/json' "$B/_all_dbs" curl -sk -o /dev/null -w 'users API, signed out -> %{http_code}\n' -H 'Accept: application/json' "$B/api/v1/users" for pair in medic:password admin:admin; do jq -nc --arg u "${pair%%:*}" --arg p "${pair#*:}" '{user:$u, password:$p}' | curl -sk -o /dev/null -w "login ${pair%%:*}/${pair#*:} -> %{http_code}\n" -H 'Content-Type: application/json' --data-binary @- "$B/medic/login" done curl -s -o /dev/null -w 'http -> %{http_code} %{redirect_url}\n' http://127.0.0.1/medic/login curl -sk -o /dev/null -w 'unknown Host -> %{http_code}\n' -H 'Host: evil.example' "$B/" || true sleep 11
sudo community-health-toolkit-roundtrip.sh | tail -1
_all_dbs, signed out -> 401 users API, signed out -> 401 login medic/password -> 401 login admin/admin -> 401 http -> 301 https://<public-ip>/medic/login unknown Host -> 000 CHT_ROUNDTRIP_OK
5

Sign in to the CHT web app

Browse to CHT_URL (https://<public-ip>/). The certificate is self-signed, so your browser warns once. Check that the fingerprint matches CHT_TLS_SHA256_FINGERPRINT and accept it for this host, or install your own certificate (Step 8).

Sign in as medic with CHT_ADMIN_PASSWORD. A wrong password is refused on the sign-in page, and no session is created.

Because medic has no place of its own, CHT opens App Management after you sign in. Click Application at the top right (or browse to https://<public-ip>/) to reach the web app.

On a new instance, the web app first shows Medic's Getting started tour of the default Maternal and Newborn Health app. Click Cancel and then Leave to close it. The tour comes back until you finish it once.

6

Add a place and a health worker

Add places and users in App Management (https://<public-ip>/admin/), or with the CHT API.

The following commands create a district hospital with a health centre under it. They then create a community health worker for the health centre with a password generated on the spot, sign in as that worker, and read both back with the administrator account. The administrator password goes to curl in a root-only config file, so it never appears on a command line.

CHT user names may contain only lower-case letters, digits, _ and -. A name stays taken after its user is deleted:

B=https://127.0.0.1;
A=$(mktemp); chmod 600 "$A"
sudo sed -n 's/^CHT_ADMIN_PASSWORD=//p' /root/community-health-toolkit-credentials.txt | { read -r P; printf 'user = "medic:%s"\n' "$P"; } > "$A"
PLACE=$(jq -nc '{name:"Riverside Health Centre", type:"health_center", parent:{name:"North District Hospital", type:"district_hospital"}}' | curl -sk -K "$A" -H 'Content-Type: application/json' --data-binary @- "$B/api/v1/places" | jq -r '.id') UPW="$(openssl rand -base64 18 | tr -dc 'A-Za-z0-9')Aa9" jq -nc --arg p "$PLACE" --arg w "$UPW" '{username:"amina_chw", password:$w, roles:["chw"], place:$p, contact:{name:"Amina Okafor"}}' | curl -sk -K "$A" -o /dev/null -w 'create user amina_chw -> %{http_code}\n' -H 'Content-Type: application/json' --data-binary @- "$B/api/v1/users" jq -nc --arg w "$UPW" '{user:"amina_chw", password:$w}' | curl -sk -o /dev/null -w 'amina_chw signs in -> %{http_code}\n' -H 'Content-Type: application/json' --data-binary @- "$B/medic/login" curl -sk -K "$A" "$B/api/v1/users" | jq -r '.[] | select(.username=="amina_chw") | "user \(.username): role \(.type), place \(.place | map(.name) | join(", ")), contact \(.contact.name)"' curl -sk -K "$A" "$B/medic/$PLACE" | jq -r '"place \(.name) (\(.type))"' curl -sk -K "$A" "$B/medic/$(curl -sk -K "$A" "$B/medic/$PLACE" | jq -r '.parent._id')" | jq -r '"parent \(.name) (\(.type))"' rm -f "$A"; unset UPW
create user amina_chw -> 200 amina_chw signs in -> 302 user amina_chw: role chw, place Riverside Health Centre, contact Amina Okafor place Riverside Health Centre (health_center) parent North District Hospital (district_hospital)

The People tab lists North District Hospital. Open it, and Riverside Health Centre is listed under Areas.

App Management > Users lists the new health worker, with a place and a contact, next to the administrator.

The 302 above is CHT sending the new worker to its change password page. A user an administrator creates must choose a new password at first sign-in, before CHT gives them a session.

Give each health worker their user name and first password over a channel you trust. Workers sign in at CHT_URL from a browser or the CHT Android app and choose their own password. The app then downloads their area's data for offline use.

7

Check the security controls

Run the following checks. The certificate was made for this instance and is a plain server certificate. Docker publishes only 80 and 443. The metadata service and a private address are refused from inside the CHT containers, and the firewall chain on the stack's bridge rejects private destinations. The units are denied metadata, the containers restart on their own with rotated logs, and the secret files are closed to other users:

openssl s_client -connect 127.0.0.1:443 -servername 127.0.0.1 </dev/null 2>/dev/null | openssl x509 -noout -subject -enddate -ext basicConstraints,extendedKeyUsage for c in $(sudo docker ps --format '{{.Names}}' | sort); do echo "$c ports: $(sudo docker port "$c" | sort | paste -sd' ' -)"; done for c in cht-api-1 cht-sentinel-1; do
sudo docker exec "$c" node -e 'const t=u=>fetch(u,{signal:AbortSignal.timeout(5000)}).then(r=>"reached "+r.status,e=>"blocked ("+((e.cause&&e.cause.code)||e.name)+")");Promise.all(["http://169.254.169.254/latest/meta-data/","http://10.0.0.1/","https://checkip.amazonaws.com/"].map(t)).then(r=>console.log(process.argv[1]+": metadata "+r[0]+", 10.0.0.1 "+r[1]+", public URL "+r[2]))' "$c" done
sudo iptables -S DOCKER-USER
sudo iptables -S SAC-CHT-EGRESS | grep -oE '\-d [0-9./]+ -j REJECT' | awk '{print $2}' | paste -sd' ' for u in docker community-health-toolkit; do echo "$u.service IPAddressDeny=$(systemctl show -p IPAddressDeny --value $u.service)"; done
sudo docker inspect --format '{{.Name}} restart={{.HostConfig.RestartPolicy.Name}} log={{.HostConfig.LogConfig.Type}}' $(sudo docker ps -q) | sort
sudo stat -c '%a %U:%G %n' /opt/cht/.env /opt/cht/secrets /opt/cht/tls /root/community-health-toolkit-credentials.txt
subject=CN = <public-ip>, O = sac notAfter=Feb 10 04:54:54 2029 GMT X509v3 Basic Constraints: critical CA:FALSE X509v3 Extended Key Usage: TLS Web Server Authentication cht-api-1 ports: cht-couchdb-1 ports: cht-haproxy-1 ports: cht-nginx-1 ports: 443/tcp -> 0.0.0.0:443 443/tcp -> [::]:443 80/tcp -> 0.0.0.0:80 80/tcp -> [::]:80 cht-nouveau-1 ports: cht-sentinel-1 ports: cht-api-1: metadata blocked (TimeoutError), 10.0.0.1 blocked (EHOSTUNREACH), public URL reached 200 cht-sentinel-1: metadata blocked (TimeoutError), 10.0.0.1 blocked (EHOSTUNREACH), public URL reached 200 -N DOCKER-USER -A DOCKER-USER -d 168.63.129.16/32 -j DROP -A DOCKER-USER -d 169.254.169.254/32 -j DROP -A DOCKER-USER -i cht-br0 -j SAC-CHT-EGRESS 0.0.0.0/8 10.0.0.0/8 100.64.0.0/10 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.0.0.0/24 192.168.0.0/16 198.18.0.0/15 224.0.0.0/4 240.0.0.0/4 172.31.0.0/16 docker.service IPAddressDeny=::ffff:169.254.169.254/128 fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32 community-health-toolkit.service IPAddressDeny=::ffff:169.254.169.254/128 fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32 /cht-api-1 restart=always log=local /cht-couchdb-1 restart=always log=local /cht-haproxy-1 restart=always log=local /cht-nginx-1 restart=always log=local /cht-nouveau-1 restart=always log=local /cht-sentinel-1 restart=always log=local 600 root:root /opt/cht/.env 700 root:root /opt/cht/secrets 700 root:root /opt/cht/tls 600 root:root /root/community-health-toolkit-credentials.txt

From outside the instance, only 22, 80 and 443 answer. CouchDB (5984 and 5986), haproxy, the API (5988) and the search service are refused even if you open those ports in the security group, because Docker never publishes them.

Sign-in throttling

CHT counts failed sign-ins. After 10 failures within 10 seconds, it answers 429 Too Many Requests for 10 seconds.

CHT counts failures per client address. nginx hands CHT the real peer address, so one client cannot lock out another and a forged X-Forwarded-For header changes nothing. CHT also counts failures per user name.

The per-user-name count is CHT's own design. Anyone who can reach port 443 can make 10 wrong guesses for a known user name such as medic and lock that name out for 10 seconds at a time. To keep that from affecting you:

  • Restrict port 443 in the security group to the networks your health workers and administrators use.
  • For day-to-day administration, create your own administrator account with a user name that is not published anywhere (App Management > Users > Add user, role Administrator). Keep medic for recovery and cht-conf.

If you see 429 while signing in, wait 10 seconds and try again.

8

Serve CHT under your own DNS name

Point a DNS record at the instance. An Elastic IP keeps the address stable. Then tell CHT the name.

The helper re-issues the self-signed certificate for the name and updates nginx's accepted names and the host CHT is told it runs on. It records the name in /etc/sac/cht-hostname (kept across reboots) and recreates the cht-nginx container:

sudo cht-set-hostname cht.example.org

Until you set a name, the instance follows its own public IP address. If it comes back from a stop and start with a new address, the certificate and settings are updated at boot.

To use a certificate from your own CA or from Let's Encrypt instead, delete /opt/cht/tls/.sac-managed so the helper never touches the certificate again. Then replace /opt/cht/tls/cert.pem (the full chain) and /opt/cht/tls/key.pem (mode 0600, owner root), and run sudo docker restart cht-nginx-1.

CHT's Android app and cht-conf both need a certificate they trust. Use a real certificate before you roll out to health workers.

9

Deploy your own CHT configuration

The image runs Medic's default configuration. Build your project's forms, tasks, targets and contact hierarchy with cht-conf on your own computer. Then deploy it to the instance as the administrator, using a certificate your computer trusts (Step 8):

npm install -g cht-conf cht --url=https://medic:<CHT_ADMIN_PASSWORD>@cht.example.org

cht-conf reads the URL from its command line. Run it from your own computer, not the instance, and clear your shell history afterwards. See the CHT app developer guides for building a configuration.

10

Restart, reboot and stop

The stack starts at every boot from the images on the disk, without pulling anything. Your data stays in /opt/cht/couchdb. To restart the stack or reboot the instance, run:

sudo systemctl restart community-health-toolkit
sudo reboot

After a reboot, the checks from Step 3 report the same results, and the place and user from Step 6 are still there.

Maintenance

  • Logs: sudo docker logs cht-api-1 (API), sudo docker logs cht-sentinel-1, sudo docker logs cht-couchdb-1, sudo docker logs cht-nginx-1 (HTTPS access), and sudo journalctl -u community-health-toolkit-firstboot.service for first boot. Container logs rotate at 20 MB, five files per container.
  • Restart: sudo systemctl restart community-health-toolkit. The containers also restart on their own if one exits.
  • Backups: CouchDB's data is in /opt/cht/couchdb. Take EBS snapshots of the root volume, or use CouchDB replication to another server. Keep /opt/cht/.env (root only) with your backups: it holds the administrator password and secrets the data was written with.
  • Operating system updates: Ubuntu's unattended security upgrades stay on, so Docker and the rest of the OS keep receiving patches. Apply other updates with sudo apt update && sudo apt upgrade in a maintenance window.
  • Upgrading CHT: the images are pinned by digest, and the stack never pulls on its own. To upgrade deliberately, read Medic's release notes and back up. Download Medic's compose files for the new release (from the same build server as the 5.3.3 ones below) and pin each image: line to its digest. Put the files in /opt/cht/compose, pull the images and run sudo systemctl restart community-health-toolkit.
  • Keep /opt/cht/compose/cht-sac.yml. It mounts the per-instance Erlang cookie file and a vm.args without the image's shared cookie, the certificate, the cht-net bridge name the firewall chain is keyed on and the core-dump limits. Never publish another port and never use host networking, which would bypass nginx and the firewall chain.

Troubleshooting

The sign-in page says the user name or password is wrong. Use medic and CHT_ADMIN_PASSWORD from /root/community-health-toolkit-credentials.txt exactly, over https://. After 10 failed attempts in 10 seconds, CHT answers 429 for 10 seconds. Wait and try again (see Sign-in throttling in Step 7).

The page does not load, or the connection is closed. nginx answers only for the instance's address and the name you set with cht-set-hostname. Browse to the exact CHT_URL, or set the name you use.

The stack did not start. On a new instance, run sudo journalctl -u community-health-toolkit-firstboot.service --no-pager | tail -20. On later boots, run sudo journalctl -u community-health-toolkit --no-pager | tail -20 and sudo docker ps -a.

An integration gets 415 Unsupported Media Type. It sent a compressed request body, which nginx refuses on this image (see the body-parser advisory under Security posture). Send the body uncompressed; responses can still be compressed.

An outbound push or SMS gateway to an address in your VPC fails. The containers are refused private, link-local and carrier-grade NAT addresses by design, so an integration must use a public HTTPS endpoint.

Support

SecureAnyCloud provides 24/7 support for this image by email and live chat. We help with launching and connecting, users and places, TLS and hostnames, configuration deployment and upgrades.

The Community Health Toolkit Core Framework is developed by Medic and contributors and is released under the GNU Affero General Public License v3.0 (AGPL-3.0). This image runs Medic's release images unmodified; the complete corresponding source for the version on the image is the 5.3.3 tag of medic/cht-core, and the stack is started with Medic's own Compose files for that release, cht-core.yml and cht-couchdb.yml, with the image lines pinned to their digests. The licence text and this source notice ship on the instance in /usr/share/doc/community-health-toolkit/. CouchDB is released by the Apache Software Foundation under the Apache License 2.0. Community Health Toolkit and CHT are names of the open source project stewarded by Medic; SecureAnyCloud is not affiliated with, endorsed by or sponsored by Medic. Ubuntu is a registered trademark of Canonical Ltd. Docker is a trademark of Docker, Inc. All other product and company names are trademarks or registered trademarks of their respective holders.

Deploy on AWS

Launch Community Health Toolkit 5.3.3 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.

View on Marketplace

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on AWS