Skip to main content

Deployment

ChartChat is deployed on a Linux server using nginx as the web server and PM2 as the Node.js process manager. All deployments are handled by Jenkins (see CI/CD Pipeline).


Server Layout

All releases live under a single deploy base:

/var/www/html/chartchat-jenkins/
├── member-portal/
│ ├── releases/
│ │ ├── release-1/ ← built static files (Vite dist)
│ │ ├── release-2/
│ │ └── release-N/ ← latest release
│ └── current ← symlink → releases/release-N (nginx root)

├── admin-portal/
│ ├── releases/
│ │ └── release-N/
│ └── current ← symlink → releases/release-N

└── server/
├── releases/
│ └── release-N/ ← dist/ + .env + medplum.config.json + node_modules/
└── current ← symlink → releases/release-N (PM2 CWD)

Each deploy creates a new numbered release directory. The current symlink is atomically switched to the new release as the final step. This means zero-downtime cutover for the portals (nginx serves from the symlink) and near-zero downtime for the server (PM2 is restarted pointing at the new path).

The pipeline keeps the 5 most recent releases and deletes older ones automatically.


Permissions

Release directories are owned by jenkins:www-data with 755 permissions so nginx (www-data) can serve the static files.

chown -R jenkins:www-data /var/www/html/chartchat-jenkins/
chmod -R 755 /var/www/html/chartchat-jenkins/

nginx

nginx serves the two frontend portals as static files. Each has its own vhost pointing at the current symlink.

Domainnginx root
dev.chartchathealth.com/var/www/html/chartchat-jenkins/member-portal/current
Admin domain/var/www/html/chartchat-jenkins/admin-portal/current

Both portals are single-page applications (React + Vite). nginx must be configured to fall back to index.html for all routes:

location / {
try_files $uri $uri/ /index.html;
}

PM2

The server runs as a Node.js process managed by PM2 under the ubuntu user.

Start command

pm2 start node --name chartchat-server --cwd /var/www/html/chartchat-jenkins/server/current -- dist/index.js aws:us-east-2:/chartchat/uat/

The aws:us-east-2:/chartchat/uat/ argument tells the server to load its configuration from AWS SSM Parameter Store instead of a local medplum.config.json.

PM2 is controlled by Jenkins via sudo

Jenkins runs as the jenkins user. To allow it to manage PM2 without a password, add this to sudoers:

jenkins ALL=(ubuntu) NOPASSWD: /home/ubuntu/.nvm/versions/node/v24.11.1/bin/pm2

File location: /etc/sudoers.d/jenkins-pm2

Why delete + start instead of restart

PM2 resolves the --cwd path at start time. If you use pm2 restart, PM2 keeps using the path from the original start command — it will not follow the updated current symlink. Every deploy therefore does:

pm2 delete chartchat-server   # remove old process entry
pm2 start node --name chartchat-server --cwd "${SERVER_CURRENT}" -- dist/index.js aws:us-east-2:/chartchat/uat/
pm2 save

Server Runtime Files

The following files must be present in the release directory when the server starts:

FileSourceNotes
dist/npm run build outputCompiled TypeScript → JavaScript
.envWritten from Jenkins credentialsNever committed to git
medplum.config.jsonNot usedServer reads config from AWS SSM at startup
node_modules/npm install inside release dirInstalled after copying dist
node_modules/@medplum/definitions/dist/Local build overrideSee note below

Why @medplum/definitions is overridden

The version of @medplum/definitions published to the npm registry does not include ChartChat's custom FHIR resource types (PatientEhrConnection, SSOIntegration, HospitalIntegration, etc.). After running npm install in the release directory, the pipeline overwrites the npm-installed copy with the locally built one:

cp -r "${WORKSPACE}/packages/definitions/dist/." \
"${RELEASE_DIR}/node_modules/@medplum/definitions/dist/"

Health Check & Rollback

After PM2 is started, the pipeline polls http://localhost:8103/healthcheck every 10 seconds for up to 4 minutes (24 attempts).

  • Success (HTTP 2xx/3xx): Deploy is marked complete.
  • Failure: The pipeline:
    1. Finds the previous release directory
    2. Repoints the current symlink back to it
    3. Runs pm2 restart chartchat-server
    4. Fails the Jenkins build so the team is alerted

The Member Portal and Admin Portal have similar rollback logic — if copying or symlinking fails, the current symlink is reverted to the previous release.


Manual Deployment Reference

The original manual setup lives at /var/www/html/chartchat-health/chartchat. Do not touch this directory — Jenkins deploys exclusively to /var/www/html/chartchat-jenkins/.