CHT Core dev environment setup

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

The Happy Path Installation

CHT Core development can be done on Linux, macOS, or Windows (using the Windows Subsystem for Linux (WSL2)). This CHT Core developer guide will have you install NodeJS, npm, and CouchDB (via Docker) on your local workstation.

Install NodeJS, npm, and Docker

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

(Node 22 is the environment used to run the CHT server in production, so this is the recommended version of Node to use for development.)

sudo apt update && sudo apt -y dist-upgrade
sudo apt -y install xsltproc curl uidmap jq python2 git make g++
# Use NVM to install NodeJS:
export nvm_version=`curl -s https://api.github.com/repos/nvm-sh/nvm/releases/latest | jq -r .name`
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/$nvm_version/install.sh | $SHELL
. ~/.$(basename $SHELL)rc
nvm install 22
# Uses Homebrew: https://brew.sh/
brew update
brew install curl jq pyenv git make node@22 gcc
# Python no longer included by default in macOS >12.3 
pyenv install 2.7.18
pyenv global 2.7.18
echo "eval \"\$(pyenv init --path)\"" >> ~/.$(basename $SHELL)rc
. ~/.$(basename $SHELL)rc
sudo apt update && sudo apt -y dist-upgrade
sudo apt -y install xsltproc curl uidmap jq python2 git make g++
# Use NVM to install NodeJS:
export nvm_version=`curl -s https://api.github.com/repos/nvm-sh/nvm/releases/latest | jq -r .name`
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/$nvm_version/install.sh | $SHELL
. ~/.$(basename $SHELL)rc
nvm install 22

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

node -v && npm -v

Install Docker:

Download and install Docker Desktop.

Alternatively, on Linux you can use the following commands to install Docker Engine. (This will reduce the layers of technical abstraction for running containers, but will not include a GUI application for managing your Docker resources.)

curl -fsSL get.docker.com -o get-docker.sh && sh get-docker.sh
# OPTIONAL: Allow user to run Docker without sudo
dockerd-rootless-setuptool.sh install
echo "export PATH=/usr/bin:$PATH" >> ~/.$(basename $SHELL)rc
echo "export DOCKER_HOST=unix:///run/user/1000/docker.sock" >> ~/.$(basename $SHELL)rc
. ~/.$(basename $SHELL)rc

Download and install Docker Desktop or Colima.

Download and install Docker Desktop.

Restart your entire machine to finish initializing Docker.

After restarting, verify Docker is running as expected. Run the simple hello-world Docker container. This should 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 https://github.com/medic/cht-core ~/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 --legacy-peer-deps

To finalise setting up any remaining dependencies build the project by running:

npm run build-dev

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

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

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:

echo $COUCH_NODE_NAME && echo $COUCH_URL

CouchDB

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

CouchDB Setup in CHT 4.x

Create a docker-compose.yml and couchdb-override.yml files under the ~/cht-docker folder with this code:

mkdir -p ~/cht-docker
curl -s -o ~/cht-docker/docker-compose.yml https://staging.dev.medicmobile.org/_couch/builds_4/medic:medic:master/docker-compose/cht-couchdb.yml
cat > ~/cht-docker/couchdb-override.yml << EOF
services:
    couchdb:
        ports:
          - "5984:5984"
          - "5986:5986"
EOF

Now you can start CouchDB. The login for your CHT instance will be medic and the password will be password:

cd ~/cht-docker 
COUCHDB_USER=medic COUCHDB_PASSWORD=password docker compose -f docker-compose.yml -f couchdb-override.yml up -d

Developing

Now you have everything installed and can begin development! You’ll need three separate terminals when doing development.

In the first terminal we’ll compile and deploy the web application by running:

cd ~/cht-core && npm run build-dev-watch

Be very patient until you see:

“Waiting…”

In the second terminal we’ll start the API nodejs service by running:

cd ~/cht-core && npm run dev-api

Finally, in a 3rd terminal we’ll start the Sentinel nodejs service by running:

cd ~/cht-core && npm run 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.

Prerequisites

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 dockerd-rootless-setuptool.sh, 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.

The workaround, unfortunately, is to just start your CouchDB Docker container with sudo: sudo docker run....

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://medic:password@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@127.0.0.1. You can find out by querying CouchDB’s membership API
  • (optional) COUCHDB_USER: the name of your CouchDB’s user. The Docker image default is medic
  • (optional) COUCHDB_PASSWORD: the credentials of your CouchDB user. The Docker image default is password
  • (optional) API_PORT: the port API will run on. If not defined, the port defaults to 5988
  • (optional) CHROME_BIN: only required if tests 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://medic:password@localhost:5984/medic

Tests

Refer to the testing doc in the GitHub repo.

nginx-local-ip

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 https://github.com/medic/nginx-local-ip.git
  2. cd into the new directory: cd nginx-local-ip
  3. Assuming your IP is 192.168.0.3, start nginx-local-ip to connect to:
    • The CHT API running via npm run or horti, execute APP_URL=http://192.168.0.3:5988 docker compose up and then access it at https://192-168-0-3.local-ip.medicmobile.org/.
    • The CHT API running via docker, the ports are remapped, so execute HTTP=8080 HTTPS=8443 APP_URL=https://192.168.0.3 docker compose up and then access it at https://192-168-0-3.local-ip.medicmobile.org:8443/.
  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 192.168.0.0/16 to any port 80,443
sudo ufw allow proto tcp from  172.16.0.0/16 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.

ngrok

  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 npm run 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. https://YOUR-NGROK-NAME.ngrok.io, 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).

pagekite

  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 npm run or horti, execute python pagekite.py 5988 YOUR-PAGEKIT-NAME.pagekite.me
  • The CHT API running via docker, execute python pagekite.py 443 YOUR-PAGEKIT-NAME.pagekite.me
  1. Access the app using the https address shown (e.g. https://YOUR-PAGEKIT-NAME.pagekite.me).