diff --git a/Dockerfile b/Dockerfile index a8bbd9a..c3eb8f4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,9 @@ FROM build-hapi AS build-distroless RUN mvn package -DskipTests spring-boot:repackage -Pboot RUN mkdir /app && cp /tmp/hapi-fhir-jpaserver-starter/target/ROOT.war /app/main.war +COPY src/main/java/HealthCheck.java /app/HealthCheck.java +RUN javac /app/HealthCheck.java + ########### Use the official Tomcat image as base image for the Tomcat variant ########### it can be built using eg. `docker build --target tomcat .` diff --git a/README.md b/README.md index 7ec71d2..3194e4c 100644 --- a/README.md +++ b/README.md @@ -475,6 +475,20 @@ jpa: # Then comment all hibernate.search.backend.* ``` +## Docker Health Check + +The distroless Docker image includes a built-in health check that verifies the FHIR server is operational by calling the `/fhir/metadata` endpoint and confirming a valid `CapabilityStatement` is returned. It uses a standalone Java class with no external dependencies, making it compatible with the distroless base image which has no shell or utilities like `curl`. + +To run the health check inside a running container: + +``` +docker exec hapi-fhir-jpaserver-start java -cp /app HealthCheck +``` + +An exit code of `0` indicates the server is healthy. An exit code of `1` indicates a failure, with diagnostic details written to stderr. + +To enable periodic health checks, uncomment the `healthcheck` block in `docker-compose.yml`. + ## Running hapi-fhir-jpaserver directly from IntelliJ as Spring Boot Make sure you run with the maven profile called ```boot``` and NOT also ```jetty```. Then you are ready to press debug the project directly without any extra Application Servers. diff --git a/docker-compose.yml b/docker-compose.yml index 543ef4b..0de85b3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,9 +8,17 @@ services: SPRING_DATASOURCE_USERNAME: "admin" SPRING_DATASOURCE_PASSWORD: "admin" SPRING_DATASOURCE_DRIVER_CLASS_NAME: "org.postgresql.Driver" - SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT: ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgresDialect + HIBERNATE_DIALECT: ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgresDialect ports: - "8080:8080" + # Uncomment to enable periodic health checks. + # Can also be run manually: docker exec hapi-fhir-jpaserver-start java -cp /app HealthCheck + # healthcheck: + # test: ["CMD", "java", "-cp", "/app", "HealthCheck"] + # interval: 30s + # timeout: 10s + # start_period: 60s + # retries: 3 depends_on: hapi-fhir-postgres: condition: service_healthy diff --git a/src/main/java/HealthCheck.java b/src/main/java/HealthCheck.java new file mode 100644 index 0000000..919162c --- /dev/null +++ b/src/main/java/HealthCheck.java @@ -0,0 +1,45 @@ +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.regex.Pattern; + +public class HealthCheck { + + public static void main(String[] args) { + try { + var port = System.getenv().getOrDefault("SERVER_PORT", "8080"); + var url = new URL("http://localhost:" + port + "/fhir/metadata"); + var conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.setRequestProperty("Accept", "application/fhir+json"); + conn.setConnectTimeout(10000); + conn.setReadTimeout(10000); + + var status = conn.getResponseCode(); + if (status != 200) { + System.err.println("Health check failed: HTTP " + status); + System.exit(1); + } + + var body = new StringBuilder(); + try (var reader = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + body.append(line); + } + } + + var pattern = Pattern.compile("\"resourceType\"\\s*:\\s*\"CapabilityStatement\""); + if (pattern.matcher(body.toString()).find()) { + System.exit(0); + } else { + System.err.println("Health check failed: CapabilityStatement not found in response"); + System.exit(1); + } + } catch (Exception e) { + System.err.println("Health check failed: " + e.getMessage()); + System.exit(1); + } + } +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 70abc71..368b366 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -120,7 +120,7 @@ spring: # Hibernate dialect is auto-detected except for H2/Postgres. # If using H2: ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect # If using Postgres: ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgresDialect - dialect: ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect + dialect: ${HIBERNATE_DIALECT:ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect} # --- Optional Hibernate DDL & tuning (commented out from source) --- hbm2ddl: