first commit
This commit is contained in:
206
hapi-overlay/Dockerfile
Normal file
206
hapi-overlay/Dockerfile
Normal file
@@ -0,0 +1,206 @@
|
||||
# =============================================================================
|
||||
# BD FHIR National — HAPI Overlay Dockerfile
|
||||
# Multi-stage build: Maven builder + lean JRE runtime
|
||||
#
|
||||
# BUILD (CI machine):
|
||||
# docker build \
|
||||
# --build-arg IG_PACKAGE=bd.gov.dghs.core-0.2.1.tgz \
|
||||
# --build-arg BUILD_VERSION=1.0.0 \
|
||||
# --build-arg GIT_COMMIT=$(git rev-parse --short HEAD) \
|
||||
# -t your-registry.dghs.gov.bd/bd-fhir-hapi:1.0.0 \
|
||||
# -f hapi-overlay/Dockerfile \
|
||||
# .
|
||||
#
|
||||
# PUSH:
|
||||
# docker push your-registry.dghs.gov.bd/bd-fhir-hapi:1.0.0
|
||||
#
|
||||
# The production server never builds — it only pulls.
|
||||
# The IG package.tgz must be present at:
|
||||
# hapi-overlay/src/main/resources/packages/${IG_PACKAGE}
|
||||
# before the build runs. CI pipeline is responsible for placing it there.
|
||||
#
|
||||
# IG VERSION UPGRADE:
|
||||
# 1. Drop new package.tgz into src/main/resources/packages/
|
||||
# 2. Update IG_PACKAGE build arg to new filename
|
||||
# 3. Rebuild and push new image tag
|
||||
# 4. Redeploy via docker-compose pull + up
|
||||
# 5. Call cache flush endpoint (see ops/version-upgrade-integration.md)
|
||||
# =============================================================================
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# STAGE 1: Builder
|
||||
# Uses full Maven + JDK image. Result discarded after JAR is built.
|
||||
# Only the fat JAR is carried forward to the runtime stage.
|
||||
# -----------------------------------------------------------------------------
|
||||
FROM maven:3.9.6-eclipse-temurin-17 AS builder
|
||||
|
||||
LABEL stage=builder
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
# Copy parent POM first — allows Docker layer caching to skip dependency
|
||||
# download if only source code changes (not POM dependencies).
|
||||
COPY pom.xml ./pom.xml
|
||||
COPY hapi-overlay/pom.xml ./hapi-overlay/pom.xml
|
||||
|
||||
# Download all dependencies into the Maven local repository cache layer.
|
||||
# This layer is invalidated only when a POM file changes.
|
||||
# On a CI machine with layer caching enabled, this saves 3-5 minutes
|
||||
# per build when only Java source changes.
|
||||
RUN mvn dependency:go-offline \
|
||||
--batch-mode \
|
||||
--no-transfer-progress \
|
||||
-pl hapi-overlay \
|
||||
-am
|
||||
|
||||
# Now copy source — this layer changes on every code commit.
|
||||
COPY hapi-overlay/src ./hapi-overlay/src
|
||||
|
||||
# Build fat JAR. Skip tests here — tests run in a separate CI stage
|
||||
# against TestContainers before the Docker build is invoked.
|
||||
# If your CI runs tests inside Docker, remove -DskipTests.
|
||||
RUN mvn package \
|
||||
--batch-mode \
|
||||
--no-transfer-progress \
|
||||
-pl hapi-overlay \
|
||||
-am \
|
||||
-DskipTests \
|
||||
-Dspring-boot.repackage.skip=false
|
||||
|
||||
# Verify the fat JAR was produced with the expected name
|
||||
RUN ls -lh /build/hapi-overlay/target/bd-fhir-hapi.jar && \
|
||||
echo "JAR size: $(du -sh /build/hapi-overlay/target/bd-fhir-hapi.jar | cut -f1)"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# STAGE 2: Runtime
|
||||
# Lean JRE image — no JDK, no Maven, no build tools.
|
||||
# Attack surface reduced. Image size ~300MB vs ~800MB for builder.
|
||||
# -----------------------------------------------------------------------------
|
||||
FROM eclipse-temurin:17-jre-jammy AS runtime
|
||||
|
||||
# Build arguments — embedded in image labels for traceability.
|
||||
# Every production image must be traceable to a specific git commit
|
||||
# and IG version. If you cannot answer "what IG version is running",
|
||||
# you cannot validate your validation engine.
|
||||
ARG BUILD_VERSION=unknown
|
||||
ARG GIT_COMMIT=unknown
|
||||
ARG IG_PACKAGE=unknown
|
||||
ARG BUILD_TIMESTAMP
|
||||
# Set default build timestamp if not provided
|
||||
RUN if [ -z "${BUILD_TIMESTAMP}" ]; then BUILD_TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ); fi
|
||||
|
||||
LABEL org.opencontainers.image.title="BD FHIR National HAPI Server" \
|
||||
org.opencontainers.image.description="National FHIR R4 repository and validation engine, Bangladesh" \
|
||||
org.opencontainers.image.vendor="DGHS/MoHFW Bangladesh" \
|
||||
org.opencontainers.image.version="${BUILD_VERSION}" \
|
||||
org.opencontainers.image.revision="${GIT_COMMIT}" \
|
||||
bd.gov.dghs.ig.version="${IG_PACKAGE}" \
|
||||
bd.gov.dghs.fhir.version="R4" \
|
||||
bd.gov.dghs.hapi.version="7.2.0"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# SYSTEM SETUP
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Create non-root user. Running as root inside a container is a security
|
||||
# vulnerability — if the JVM is exploited, the attacker gets root on the host
|
||||
# if the container runs privileged or has volume mounts.
|
||||
RUN groupadd --gid 10001 hapi && \
|
||||
useradd --uid 10001 --gid hapi --shell /bin/false --no-create-home hapi
|
||||
|
||||
# Install curl for Docker health checks.
|
||||
# tini: init process to reap zombie processes and forward signals correctly.
|
||||
# Without tini, SIGTERM from docker stop is not forwarded to the JVM and
|
||||
# the container is killed after the stop timeout (ungraceful shutdown).
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
tini \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Application directory
|
||||
WORKDIR /app
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# COPY ARTIFACTS FROM BUILDER
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
COPY --from=builder /build/hapi-overlay/target/bd-fhir-hapi.jar /app/bd-fhir-hapi.jar
|
||||
|
||||
# Set correct ownership — hapi user must be able to read the JAR
|
||||
RUN chown hapi:hapi /app/bd-fhir-hapi.jar
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# RUNTIME CONFIGURATION
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Switch to non-root user before any further commands
|
||||
USER hapi
|
||||
|
||||
# JVM tuning arguments.
|
||||
# These are defaults — override via JAVA_OPTS environment variable
|
||||
# in docker-compose.yml for environment-specific tuning.
|
||||
#
|
||||
# -XX:+UseContainerSupport
|
||||
# Enables JVM to read CPU/memory limits from cgroup (Docker constraints).
|
||||
# Without this, JVM reads host machine memory and over-allocates heap.
|
||||
# Available since Java 8u191 — always present in temurin:17.
|
||||
#
|
||||
# -XX:MaxRAMPercentage=75.0
|
||||
# Heap = 75% of container memory limit.
|
||||
# For a 2GB container: heap = 1.5GB. Remaining 512MB for non-heap
|
||||
# (Metaspace, thread stacks, code cache, direct buffers).
|
||||
# HAPI 7.x with full IG loaded needs ~512MB heap minimum.
|
||||
# Recommended container memory: 2GB minimum, 4GB for production.
|
||||
#
|
||||
# -XX:+ExitOnOutOfMemoryError
|
||||
# Kill the JVM immediately on OOM instead of limping along in a broken
|
||||
# state. Docker will restart the container. Prefer clean restart over
|
||||
# degraded service.
|
||||
#
|
||||
# -Djava.security.egd=file:/dev/urandom
|
||||
# Prevents SecureRandom from blocking on /dev/random in containerised
|
||||
# environments where hardware entropy is limited.
|
||||
# Critical for JWT validation performance — Nimbus JOSE uses SecureRandom.
|
||||
ENV JAVA_OPTS="\
|
||||
-XX:+UseContainerSupport \
|
||||
-XX:MaxRAMPercentage=75.0 \
|
||||
-XX:+ExitOnOutOfMemoryError \
|
||||
-XX:+HeapDumpOnOutOfMemoryError \
|
||||
-XX:HeapDumpPath=/tmp/heapdump.hprof \
|
||||
-Djava.security.egd=file:/dev/urandom \
|
||||
-Dfile.encoding=UTF-8 \
|
||||
-Duser.timezone=UTC"
|
||||
|
||||
# Spring profile — overridable via environment variable in docker-compose
|
||||
ENV SPRING_PROFILES_ACTIVE=prod
|
||||
|
||||
# FHIR server base URL — must match nginx configuration
|
||||
ENV HAPI_FHIR_SERVER_ADDRESS=https://fhir.dghs.gov.bd/fhir
|
||||
|
||||
# Expose HTTP port. nginx terminates TLS and proxies to this port.
|
||||
# Do NOT expose this port directly — it must only be reachable via nginx.
|
||||
EXPOSE 8080
|
||||
|
||||
# Health check — used by Docker and docker-compose depends_on condition.
|
||||
# /actuator/health returns 200 when application is fully started and
|
||||
# all health indicators pass (including the custom AuditDataSourceHealthIndicator).
|
||||
# --fail-with-body: return non-zero exit on HTTP error responses.
|
||||
# start_period: allow 120s for startup (IG loading + Flyway migrations).
|
||||
HEALTHCHECK \
|
||||
--interval=30s \
|
||||
--timeout=10s \
|
||||
--start-period=120s \
|
||||
--retries=3 \
|
||||
CMD curl --fail --silent --show-error \
|
||||
http://localhost:8080/actuator/health/liveness || exit 1
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# ENTRYPOINT
|
||||
# tini as PID 1 → JVM as child process.
|
||||
# tini handles SIGTERM correctly: forwards to JVM, waits for graceful
|
||||
# shutdown, then exits. Without tini, docker stop sends SIGTERM to PID 1
|
||||
# (the JVM) but the JVM may ignore it depending on signal handling setup.
|
||||
# -----------------------------------------------------------------------------
|
||||
ENTRYPOINT ["/usr/bin/tini", "--"]
|
||||
CMD ["sh", "-c", "exec java ${JAVA_OPTS} -jar /app/bd-fhir-hapi.jar"]
|
||||
Reference in New Issue
Block a user