Documentation
Deploying Clinical Extract as one container in your environment.
Clinical Extract is installed inside your environment. We build the image from the release source with the Dockerfile, keep it in your container registry and configure the EHR connection. It runs as one container on your compute, under your access controls. The signing key is kept in your key store, output is written to your storage, and the only outbound connections go to your EHR.
1 The install
One container, built and installed by us.
We install Clinical Extract inside your environment, in your cloud account or your data center. Each release is source with a Dockerfile: a node:22-alpine image that installs production dependencies and runs node src/server.js on port 3000, with a health check on /healthz. We build the image where it runs, keep it in your container registry when your platform needs one, and configure the EHR connection. The build stamps the release and the build time into the image, and /api/version reports them.
2 Where
The same image on a VM, a managed container service or a small instance.
- A VM with Docker Compose
- The container binds to localhost; your reverse proxy (Caddy or nginx) terminates TLS, publishes the JWKS path and puts basic auth in front of everything else. The key is a file mounted read-only.
- AWS App Runner
- The image from your ECR repository, port 3000, health check
/healthz; the key and the dashboard login as secrets; TLS and scaling included. - AWS Lightsail containers
- A flat-rate container service; one script builds, pushes and deploys, with the same secrets.
- A small EC2
- The VM pattern on an instance in your AWS account, with the same Compose file and proxy.
3 Settings
Six settings per EHR connection.
| Setting | What it is | Where it comes from |
|---|---|---|
| OAuth base | The EHR’s OAuth base for the connection; the token endpoint and the FHIR base derive from it (or the two are given as returned by the vendor). | The EHR page for your vendor |
| Client ID | The backend app’s client ID the vendor issued at registration. | The vendor’s app registration |
| Private key | The RSA key that signs every token request, as a file mounted read-only or a base64 secret your platform injects. | Generated in your environment at install; it stays there |
| Group ID | The study cohort’s FHIR Group, one per study. | Your EHR team, or the cohort builder |
| Dashboard login | A user and password for the built-in login, when no reverse proxy fronts the container. | You |
| Poll pacing | The minimum status-poll interval and the cap; Retry-After from the EHR wins when present. | Defaults; change for testing |
The table scrolls sideways.
4 Keys and the JWKS
The private key never leaves your environment.
The RSA key pair is generated where the container runs, during the install. The private key signs every token request (RS384) and is read from a file mounted read-only or from a secret your platform injects at boot. The public key is served at /.well-known/jwks.json with a key ID derived from the key itself, so the JWKS and the JWT header always agree; the vendor registers that URL, or takes the certificate as an upload. The JWKS answers with a five-minute cache header, so a rotated key is picked up promptly; the server log records every fetch of it.
5 Network, storage, log
Outbound only to your EHR, with one public inbound path.
- Outbound
- HTTPS on port 443 to your EHR: the token endpoint,
Group/{id}/$export, the status URL, each file URL. Nothing reaches us. - Inbound
- The JWKS path, public, for the EHR vendor; the dashboard and its API behind your proxy's authentication or the built-in login.
- Storage
- The data directory, mounted from your storage: the NDJSON downloads and each study's folder with study.csv and data-dictionary.json.
- Log
- A size-rotated server log in the same directory with each run step, token request and JWKS fetch, yours to keep and ship to your SIEM.
6 Questions
Questions about deployment
Where does the image come from?
We build it inside your environment. Each release ships as source with its Dockerfile (node:22-alpine). On a VM, we build and start it with docker compose up -d --build; for App Runner, Lightsail or EC2, we build it and push it to your container registry. The image stays in your environment.
What does the container reach out to?
Your EHR, over HTTPS on port 443: the token endpoint, the Group kickoff, the status URL and each file URL. Nothing else. The one inbound path that is public is the JWKS, which the EHR vendor fetches to verify the signed JWT.
How do we rotate the key?
We set up key rotation with your team during the install. To rotate, generate a new key pair in your environment, restart the container, and the vendor picks up the new key ID from the JWKS URL. Keep the previous key in the set briefly while assertions are in flight, then drop it. A vendor that took an uploaded certificate takes the new one the same way.
Where do the files go?
Into the container’s data directory, mounted from your storage: the NDJSON downloads, each study’s folder with study.csv and data-dictionary.json, and the size-rotated server log.
How do we know what is deployed?
/api/version answers with the release and the build time stamped into the image at build; /healthz answers {"ok":true} for your health checks.
Next
See Clinical Extract run on one of your studies.
Tell us which EHR you run and what the study or registry needs. We reply within one business day to set a meeting time.