CHT Core dev environment setup

Getting your local machine ready to development work on CHT Core.

The Happy Path Installation

This CHT Core developer guide will have you install NodeJS, npm, Grunt and CouchDB (via Docker) on your local workstation. These instructions should work verbatim on Ubuntu 18-22 (see Ubuntu 18 note), but will need tweaks for MacOS (via brew, see MacOS > 12.3 note) or Windows (via WSL2).

Install NodeJS, npm, grunt and Docker

First, update your current Ubuntu packages and install some supporting tools via apt:

sudo apt update && sudo apt -y dist-upgrade
sudo apt -y install xsltproc curl uidmap jq python2 git make g++

Then install nvm, add it to your path and install NodeJS 16:

export nvm_version=`curl -s | jq -r .name`
curl -o-$nvm_version/ | $0
. ~/.$0rc
nvm install 16

Now let’s ensure NodeJS 16 and npm 8 were installed. This should output version 16.x.x for NodeJS and 8.x.x for npm:

node -v && npm -v

With NodeJS out of the way, let’s install grunt via npm:

npm install -g grunt-cli

Install Docker:

curl -fsSL -o && sh

It’s easier if you don’t always have to run sudo for all your Docker calls, so let’s set that up: install
echo "export PATH=/usr/bin:$PATH" >> ~/.$0rc
echo "export DOCKER_HOST=unix:///run/user/1000/docker.sock" >> ~/.$0rc
. ~/.$0rc

In order for Docker to boot correctly, restart entire machine, which will complete the “Install” Section:

sudo reboot

To verify Docker is running as expected, let’s run the simple hello-world Docker container. This output “Hello from Docker!” as well as some other intro text:

docker run hello-world

CHT Core Cloning and Setup

Clone the main CHT Core repo from GitHub and change directories into it:

git clone ~/cht-core
cd ~/cht-core

Install dependencies and perform other setup tasks via an npm command. Note this command may take many minutes. Be patient!

npm ci

If you encounter conflicting dependencies, run the following command:

npm ci --legacy-peer-deps


CouchDB execution differs depending on whether you’re running CHT 3.x or 4.x. Follow the instructions in one of the sections below.

CouchDB Setup in CHT 3.x

Note this will run in the background and store its data in /home/YOUR-USER/cht-docker. The login for your CHT instance will be medic and the password will be password:

docker run -d -p 5984:5984 -p 5986:5986 --name medic-couchdb -e COUCHDB_USER=medic -e COUCHDB_PASSWORD=password -v ~/cht-docker/local.d:/opt/couchdb/data -v ~/cht-docker/local.d:/opt/couchdb/etc/local.d apache/couchdb:2

Let’s ensure CouchDB is set up with a test curl call. This should show “nonode@nohost” in JSON:

curl -X GET "http://medic:password@localhost:5984/_membership" | jq

Every time you run any grunt or node commands, it will expect COUCH_NODE_NAME and COUCH_URL environment variables to be set:

echo "export COUCH_NODE_NAME=nonode@nohost">> ~/.$0rc
echo "export COUCH_URL=http://medic:password@localhost:5984/medic">> ~/.$0rc
. ~/.$0rc

To ensure these to exports and sourcing your rc file worked, echo the values back out. You should see nonode@nohost and http://medic:password@localhost:5984/medic:


You need to harden CouchDB with a grunt call, required even in development:

grunt secure-couchdb
curl -X PUT "http://medic:password@localhost:5984/_node/$COUCH_NODE_NAME/_config/httpd/WWW-Authenticate" -d '"Basic realm=\"administrator\""' -H "Content-Type: application/json"

CouchDB Setup in CHT 4.x

Create a docker-compose.yml file under the couchdb folder.

Copy the content of the CouchDB docker-compose file from the of the CHT release version you are trying to run locally. For example, this is the file for the most recent build.

Startup CouchDB:

cd couchdb 
docker-compose up


Now you have everything installed and can begin development! You’ll need three separate terminals when doing development. In the first terminal, run:

cd ~/cht-core && grunt

Be very patient until you see:


Then in a 2nd terminal run:

cd ~/cht-core && grunt dev-api

Finally, in a 3rd terminal run:

cd ~/cht-core && grunt dev-sentinel

That’s it! Now when you edit code in your IDE, it will automatically reload. You can see the CHT running locally here: http://localhost:5988/

When you’re done with development you can ctrl + c in the three terminals and stop the CouchDB container with docker stop medic-couchdb. When you want to resume development later, run docker start medic-couchdb and re-run the three terminal commands.

Other Path Troubleshooting

If you weren’t able to follow the happy path above, here are some details about the developer install that may help you troubleshoot what went wrong.


If you had issues with following the above steps, check out these links for how to install the prerequisites on your specific platform:

Ubuntu 18.04

Ubuntu 18.04’s default apt repositories do not know about python2. This means when you go to install run the first apt install command above, you see an error:

E: Unable to locate package python2

To fix this, change the apt install call to this:

sudo apt -y install xsltproc curl uidmap jq python git make g++

As well, after you install docker, and go to run the rootless script, you might see this error:

[ERROR] Failed to start docker.service. Run `journalctl -n 20 --no-pager --user --unit docker.service` to show the error log.

To work around, unfortunately, is to just start your CouchDB Docker container with sudo: sudo docker run....

MacOS > 12.3

Apple removed the system-provided python2 installation starting with MacOS version 12.3. This means when you run the npm ci command above, you see an error:

npm ERR! gyp ERR verb find Python Python is not set from command line or npm configuration  

To fix this, run the following commands:

brew install pyenv
pyenv install 2.7.18
pyenv global 2.7.18
echo "eval \"\$(pyenv init --path)\"" >> ~/.$0rc
. $0

CouchDB on Docker Details

Breaking down the command from the above section, here’s a generic version that doesn’t include hard coded paths:

docker run -d -p 5984:5984 -p 5986:5986 --name medic-couchdb -e COUCHDB_USER=medic -e COUCHDB_PASSWORD=password -v <data path>:/opt/couchdb/data -v <config path>:/opt/couchdb/etc/local.d apache/couchdb:2

Parts of the command:

  • --name creates a container called medic-couchdb. You can name it whatever you want, but this is how you refer to it later
  • -e sets an environment variable inside the container. Two are set here, for a user and password for the initial admin user.
  • -v maps where couchdb stores data to your local file system to ensure persistence without depending on the container, using the path before the : (the path after the colon is the internal path inside the docker image). This should be somewhere you have write access to, and want this data to be stored. The second mounted volume is for the couch configuration, which will retain settings if your container is removed. This is especially important after running the command to secure the instance (done in steps below).
  • apache/couchdb:2 will install the latest package for CouchDB 2.x

Once this downloads and starts, you will need to initialise CouchDB as noted in their install instructions.

You can use docker stop medic-couchdb to stop it and docker start medic-couchdb to start it again. Remember that you’ll need to start it whenever you restart your OS, which might not be the case if you use a normal OS package. docker rm medic-couchdb will totally remove the container.

Medic recommends you familiarise yourself with other Docker commands to make docker image and container management clearer.

Required environment variables

Medic needs the following environment variables to be declared:

  • COUCH_URL: the full authenticated url to the medic DB. Locally this would be http://myadminuser:myadminpass@localhost:5984/medic
  • COUCH_NODE_NAME: the name of your CouchDB’s node. The Docker image default is nonode@nohost. Other installations may use couchdb@ You can find out by querying CouchDB’s membership API
  • (optionally) API_PORT: the port API will run on. If not defined, the port defaults to 5988
  • (optionally) CHROME_BIN: only required if grunt unit or grunt e2e complain that they can’t find Chrome or if you want to run a specific version of the Chrome webdriver.

How to permanently define environment variables depends on your OS and shell (e.g. for bash you can put them ~/.bashrc). You can temporarily define them with export:

export COUCH_NODE_NAME=nonode@nohost
export COUCH_URL=http://myadminuser:myadminpass@localhost:5984/medic


Refer to the testing doc in the GitHub repo.


nginx-local-ip is a local proxy that keeps all traffic local, and runs without latency or throttling. If sharing your local CHT instance is not required, it is the recommended method to add a valid SSL certificate (rather than ngrok or similar).

  1. Clone the repo: git clone
  2. cd into the new directory: cd nginx-local-ip
  3. Assuming your IP is, start nginx-local-ip to connect to:
  4. The HTTP/HTTPS ports (80/443) need to accept traffic from the IP address of your host machine and your local webapp port (e.g. 5988) needs to accept traffic from the IP address of the nginx-local-ip container (on the Docker network). If you are using the UFW firewall (in a Linux environment) you can allow traffic on these ports with the following commands:

(Since local IP addresses can change over time, ranges are used in these rules so that the firewall configuration does not have to be updated each time a new address is assigned.)

sudo ufw allow proto tcp from to any port 80,443
sudo ufw allow proto tcp from to any port 5988

Remote Proxies

ngrok and pagekite are remote proxies that route local traffic between your client and the CHT via a remote SSL terminator. While easy and handy, they introduce latency and are sometimes throttled. Always use nginx-local-ip when you need a TLS certificate and only use these when you need to share your dev instance.


  1. Create an ngrok account, download and install the binary, then link your computer to your ngrok account.
  2. Start ngrok to connect to:
    • The CHT API running via grunt or horti, execute ./ngrok http 5988
    • The CHT API running via docker, execute ./ngrok http 443
  3. Access the app using the https address shown (e.g., replacing YOUR-NGROK-NAME with what you signed up with).

Note: The service worker cache preload sometimes fails due to connection throttling (thereby causing an ngrok failure at startup).


  1. Create a pagekite account, download and install the python script.
  2. Start pagekite (be sure to replace YOUR-PAGEKIT-NAME with the URL you signed up for) to connect to:
  • The CHT API running via grunt or horti, execute python 5988
  • The CHT API running via docker, execute python 443
  1. Access the app using the https address shown (e.g.