Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
edeb9fe
build: add secret-free Docker image for chpl-api
tmy1313 Jul 22, 2026
7dbfa2a
ci: publish chpl-api Docker image to GHCR
tmy1313 Jul 22, 2026
1cf2ee3
test: temporarily trigger docker-publish on pull_request
tmy1313 Jul 22, 2026
eafd1a8
ci: tag chpl-api image with build number, sha, and branch pointer
tmy1313 Jul 22, 2026
f11e1e5
docs: document required env vars for the chpl-api Docker image
tmy1313 Jul 22, 2026
5816d90
ci: disable provenance/sbom attestations for chpl-api image push
tmy1313 Jul 22, 2026
42578db
Revert "test: temporarily trigger docker-publish on pull_request"
tmy1313 Jul 22, 2026
419ec7d
ci: build chpl-api image on merge to environment branches
tmy1313 Jul 22, 2026
f9fad67
chore(docker): remove unused JWK_KEY signing-key mechanism
tmy1313 Jul 23, 2026
636e109
ci: add .dockerignore to shrink chpl-api image build context
tmy1313 Jul 23, 2026
7d1ede2
chore: remove orphaned .gitattributes rule for deleted entrypoint.sh
tmy1313 Jul 23, 2026
59e779b
ci: cancel in-flight docker-publish runs for the same branch
tmy1313 Jul 23, 2026
ea9b5d4
ci: run unit tests before building docker image
tmy1313 Jul 23, 2026
26a13a1
ci: fail fast on compile errors before building docker image
tmy1313 Jul 23, 2026
cfb9ae6
test: temporarily break a unit test to verify CI gate
tmy1313 Jul 23, 2026
70ab194
Revert "test: temporarily break a unit test to verify CI gate"
tmy1313 Jul 23, 2026
1d610f9
test: temporarily break compilation to verify CI gate
tmy1313 Jul 23, 2026
6892a31
Revert "test: temporarily break compilation to verify CI gate"
tmy1313 Jul 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.git
**/target/
node_modules
.vscode
.claude
120 changes: 120 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
name: Publish Docker Image

on:
workflow_dispatch: {}
# One workflow, shared by every environment branch: a merge into any of
# these triggers a build tagged latest-<that-branch>. No per-branch copies
# or separate workflow files to keep in sync.
push:
branches:
- development
- qa
- staging
- production

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I think we want to get to PROD eventually, I'd like to make sure we don't touch PROD at all until we're comfortable with the lower environments, so can we take this branch out of the list for now?


# Publishes a floating latest-<branch> tag - without this, two runs for the
# same branch could race and let an older commit's build finish last,
# overwriting latest-<branch> with a stale image.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read
packages: write

jobs:
# Mirrors the "Run API Unit Tests" job in the Bamboo CHPL Development
# Deployment plan (chpl-build/chpl-build-dev): `mvn clean test` against
# chpl/pom.xml on JDK 21. Gates the image build so a broken build never
# gets published.
unit-tests:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'

- name: Run unit tests
env:
MAVEN_OPTS: -Xms512m -Xmx1024m
run: mvn -B clean test --file chpl/pom.xml

# Mirrors the "Build API" job (CompileApiTask) in the same Bamboo plan, and
# runs the exact `mvn clean package -DskipTests` command that docker/Dockerfile's
# build stage runs - so a compile/packaging failure shows up here, fast,
# instead of partway through the (slower) Docker build.
compile:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'

- name: Compile and package
env:
MAVEN_OPTS: -Xms512m -Xmx1024m
run: mvn -B clean package -DskipTests --file chpl/pom.xml

build-and-push:
needs: [unit-tests, compile]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Compute image metadata
id: meta
run: |
echo "image=ghcr.io/$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT"
echo "sha_short=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
echo "branch=$(echo '${{ github.ref_name }}' | tr '/' '-')" >> "$GITHUB_OUTPUT"

# This image contains no secrets - see docker/Dockerfile. Every environment
# (development/qa/staging/production) runs the exact same image; secrets are
# supplied as container environment variables at `docker run` time, not baked
# in here.
#
# Tags:
# build-<run_number> - immutable, sequential build number (github.run_number
# never repeats or goes backwards for this workflow)
# sha-<short-sha> - immutable, traces back to the exact commit
# latest-<branch> - floating pointer to the most recent build from that
# branch (latest-development, latest-qa, latest-staging,
# latest-production once triggered from those branches)
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: docker/Dockerfile
push: true
# build-push-action v6 enables these by default, which each push a
# separate untagged provenance/SBOM manifest to GHCR alongside the
# real image - not needed here, so turned off to keep the package
# version list clean.
provenance: false
sbom: false
tags: |
${{ steps.meta.outputs.image }}:build-${{ github.run_number }}
${{ steps.meta.outputs.image }}:sha-${{ steps.meta.outputs.sha_short }}
${{ steps.meta.outputs.image }}:latest-${{ steps.meta.outputs.branch }}
45 changes: 45 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Stage 1: Build the WAR file using Maven
FROM maven:3.9-eclipse-temurin-21-alpine AS build
WORKDIR /app
Comment thread
tmy1313 marked this conversation as resolved.

# Copy the pom.xml files first to leverage Docker layer caching
# This ensures that if only source code changes, dependencies are not re-downloaded
COPY chpl/pom.xml .
COPY chpl/chpl-api/pom.xml chpl-api/
COPY chpl/chpl-resources/pom.xml chpl-resources/
COPY chpl/chpl-service/pom.xml chpl-service/

# Copy the rest of the project files
COPY chpl/chpl-api/lombok.config chpl-api/lombok.config
COPY chpl/chpl-api/src chpl-api/src
COPY chpl/chpl-resources/src chpl-resources/src
COPY chpl/chpl-service/lombok.config chpl-service/lombok.config
COPY chpl/chpl-service/src chpl-service/src

RUN mvn clean package -DskipTests

# Stage 2: Deploy to Tomcat
#
# This image contains no secrets and is identical across every environment
# (dev/qa/stage/production). Tomcat config below only has ${ENV_VAR}
# placeholders (resolved at container startup - see catalina.properties'
# org.apache.tomcat.util.digester.PROPERTY_SOURCE) and the checked-in
# environment.properties/email.properties already mark every sensitive key
# as SECRET, relying on Spring's Environment to prefer OS environment
# variables over those classpath files.
#
# Required environment variables at `docker run` time:
# DB_URL, DB_USERNAME, DB_PASSWORD - jdbc/openchpl datasource
# plus every property marked SECRET in
# chpl/chpl-resources/src/main/resources/environment.properties and email.properties
# (e.g. SPRING_REDIS_PASSWORD, COGNITO_SECRETKEY, AZURE_CLIENTSECRET_ONC, JIRA_PASSWORD, ...)
FROM tomcat:11.0.23-jdk21

COPY docker/tomcat-conf/server.xml /usr/local/tomcat/conf/server.xml
COPY docker/tomcat-conf/context.xml /usr/local/tomcat/conf/context.xml
COPY docker/tomcat-conf/catalina.properties /usr/local/tomcat/conf/catalina.properties

COPY --from=build /app/chpl-api/target/chpl-service.war /usr/local/tomcat/webapps/chpl-service.war

# Expose our custom Tomcat port
EXPOSE 8181
147 changes: 147 additions & 0 deletions docker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# Running the chpl-api Docker image

`docker/Dockerfile` builds a **secret-free** image: the same image is used for
every environment (development/qa/staging/production). Nothing sensitive is
baked in at build time - all of it is supplied as container environment
variables when the image is actually run. See `docker/Dockerfile` and
`docker/tomcat-conf/` for how that works mechanically (Tomcat's
`EnvironmentPropertySource` for `server.xml`, Spring's `Environment` for
everything else).

This doc lists every environment variable the image needs or accepts. It does
**not** contain real values for any environment - only what each variable is
for and what kind of value it expects. Real values for existing environments
currently live in [AudaciousInquiry/chpl-build](https://github.com/AudaciousInquiry/chpl-build)
(`chpl-build-{dev,qa,stg,prod}/src/main/resources/override-api-properties*.sh`)
- that repo should stay private, and its values should never be copied into
this one.

## Quick start

```sh
docker run -d --name chpl-api \
-p 8181:8181 \
-e DB_URL="jdbc:postgresql://<host>:5432/openchpl" \
-e DB_USERNAME="<db-username>" \
-e DB_PASSWORD="<db-password>" \
-e chplUrlBegin="https://chpl-dev.healthit.gov" \
-e SPRING_REDIS_HOST="<redis-host>" \
-e SPRING_REDIS_PORT="6379" \
-e SPRING_REDIS_PASSWORD="<redis-password>" \
ghcr.io/<owner>/chpl-api:latest-development
```

Add `-e` flags for whichever of the feature-area variables below apply to
your environment - omitted ones just leave that feature unconfigured.

Note: property keys that contain dots (like `spring.redis.host`) are **not**
valid POSIX environment variable names on every shell/platform - Spring will
also match the all-caps/underscore form (`SPRING_REDIS_HOST`). Use whichever
form your deployment tooling handles more easily; both resolve to the same
property.

## Required to start at all

| Env var | Used for |
|---|---|
| `DB_URL` | JDBC URL for the `jdbc/openchpl` datasource (`server.xml`) |
| `DB_USERNAME` | DB username (`server.xml`) |
| `DB_PASSWORD` | DB password (`server.xml`) |

## Filesystem paths (need a mounted volume, not just an env var)

These properties are file/directory paths the app reads or writes at runtime.
Mount a volume at the path you set, or the app will fail writing to a
read-only container filesystem.

| Env var | Property | Purpose |
|---|---|---|
| `downloadFolderPath` | `downloadFolderPath` | Where generated downloadable files are written |
| `auditDataFilePath` | `auditDataFilePath` | Where audit data backups are written |

## Environment-dependent (not secret, but should differ per environment)

| Env var | Property | Purpose | Example shape |
|---|---|---|---|
| `chplUrlBegin` | `chplUrlBegin` | Public base URL for this environment | `https://chpl-qa.healthit.gov` |
| `EMAILBUILDER_CONFIG_EMAILSUBJECTSUFFIX` | `emailBuilder.config.emailSubjectSuffix` | Tag appended to outgoing email subjects | `[QA]` |
| `SERVER_ENVIRONMENT` | `server.environment` | `non-production` or `production` | |
| `REPORT_ENVIRONMENT` | `report.environment` | Label used on generated reports | `DEV`, `QA`, `STAGE`, `PROD` |

## Feature-area secrets

Every property below is marked `SECRET` in
[`environment.properties`](../chpl/chpl-resources/src/main/resources/environment.properties)
or [`email.properties`](../chpl/chpl-resources/src/main/resources/email.properties)
- meaning the app has no usable default and one of these env vars must be set
for that feature to work. If a feature isn't used in a given environment, its
variables can be omitted.

**Database / cache**
| Env var | Property |
|---|---|
| `SPRING_REDIS_HOST` | `spring.redis.host` |
| `SPRING_REDIS_PORT` | `spring.redis.port` |
| `SPRING_REDIS_PASSWORD` | `spring.redis.password` |

**AWS Cognito (authentication)**
| Env var | Property |
|---|---|
| `COGNITO_ACCESSKEY` | `cognito.accessKey` |
| `COGNITO_SECRETKEY` | `cognito.secretKey` |
| `COGNITO_REGION` | `cognito.region` |
| `COGNITO_USERPOOLID` | `cognito.userPoolId` |
| `COGNITO_USERPOOLCLIENTSECRET` | `cognito.userPoolClientSecret` |
| `COGNITO_CLIENTID` | `cognito.clientId` |
| `COGNITO_ENVIRONMENT_GROUPNAME` | `cognito.environment.groupName` |
| `COGNITO_SYSTEMUSERUUID` | `cognito.systemUserUuid` |
| `COGNITO_ANONYMOUSUSERUUID` | `cognito.anonymousUserUuid` |

**ONC Azure AD**
| Env var | Property |
|---|---|
| `AZURE_USER_ONC` | `azure.user.onc` |
| `AZURE_CLIENTID_ONC` | `azure.clientId.onc` |
| `AZURE_CLIENTSECRET_ONC` | `azure.clientSecret.onc` |
| `AZURE_TENANTID_ONC` | `azure.tenantId.onc` |

**JIRA**
| Env var | Property |
|---|---|
| `JIRA_USERNAME` | `jira.username` |
| `JIRA_PASSWORD` | `jira.password` |

**Datadog**
| Env var | Property |
|---|---|
| `DATADOG_APIKEY` | `datadog.apiKey` |
| `DATADOG_APPKEY` | `datadog.appKey` |

**AIA (Real World Testing validation)**
| Env var | Property |
|---|---|
| `AIA_AUTHENTICATE_CLIENTSECRET` | `aia.authenticate.clientSecret` |
| `AIA_AUTHENTICATE_CLIENTID` | `aia.authenticate.clientId` |

**FF4J admin console**
| Env var | Property |
|---|---|
| `FF4J_WEBCONSOLE_USERNAME` | `ff4j.webconsole.username` |
| `FF4J_WEBCONSOLE_PASSWORD` | `ff4j.webconsole.password` |

**Email / notifications**
| Env var | Property |
|---|---|
| `internalErrorEmailRecipients` | `internalErrorEmailRecipients` |
| `internalFutureCertificationStatusEmailRecipients` | `internalFutureCertificationStatusEmailRecipients` |
| `emailBuilder_config_forwardAddress` | `emailBuilder_config_forwardAddress` |
| `DIRECTREVIEW_CHPLCHANGES_EMAIL` | `directReview.chplChanges.email` |
| `DIRECTREVIEW_UNKNOWNCHANGES_EMAIL` | `directReview.unknownChanges.email` |

## Anything not listed here

`environment.properties`, `email.properties`, `lookup.properties`, and
`errors.properties` (all in `chpl/chpl-resources/src/main/resources/`) have
sensible non-secret defaults for everything else, and can still be overridden
the same way (env var name = property name, dots -> underscores, upper-cased)
if a particular deployment needs to tune something not listed above.
Loading
Loading