diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml
new file mode 100644
index 00000000..51625730
--- /dev/null
+++ b/.github/workflows/container.yml
@@ -0,0 +1,39 @@
+name: Container Image
+
+on:
+ push:
+ branches: [main]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: write
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Java 25
+ uses: actions/setup-java@v4
+ with:
+ java-version: '25'
+ distribution: 'temurin'
+ cache: 'maven'
+
+ - name: Build and push container image
+ run: >
+ mvn -B package -DskipTests
+ -Dquarkus.container-image.build=true
+ -Dquarkus.container-image.push=true
+ -Dquarkus.container-image.tag=latest
+ -Dquarkus.container-image.username=${{ github.actor }}
+ -Dquarkus.container-image.password=${{ secrets.GITHUB_TOKEN }}
+
+ - name: Clean up old untagged container versions
+ uses: actions/delete-package-versions@v5
+ with:
+ package-name: h5m
+ package-type: container
+ delete-only-untagged-versions: true
+ min-versions-to-keep: 0
diff --git a/docs/site/content/en/docs/deployment/docker.md b/docs/site/content/en/docs/deployment/docker.md
index e022ed87..96632065 100644
--- a/docs/site/content/en/docs/deployment/docker.md
+++ b/docs/site/content/en/docs/deployment/docker.md
@@ -5,61 +5,23 @@ description: Running h5m in a Docker container.
draft: false
---
-{{< alert color="warning" >}}
-h5m does not currently publish an official Docker image. The examples on this page show how to build and run your own container image using standard Quarkus patterns.
-{{< /alert >}}
+## Container Image
-## Build the JAR First
+A container image is published to GitHub Container Registry on every push to `main`:
```bash
-git clone https://github.com/hyperfoil/h5m.git
-cd h5m
-mvn clean package
-```
-
-## Create a Dockerfile
-
-Place this `Dockerfile` in the project root:
-
-```dockerfile
-FROM eclipse-temurin:21-jre
-
-WORKDIR /app
-
-COPY target/h5m.jar /app/h5m.jar
-
-# Data directory for SQLite database
-RUN mkdir -p /data && \
- useradd -r -u 1001 h5m && \
- chown h5m:h5m /data
-
-USER 1001
-
-EXPOSE 8080
-
-ENV QUARKUS_DATASOURCE_DB_KIND=sqlite
-ENV QUARKUS_DATASOURCE_JDBC_URL=jdbc:sqlite:/data/h5m.db
-
-ENTRYPOINT ["java", "-jar", "/app/h5m.jar"]
-```
-
-## Build the Image
-
-```bash
-docker build -t h5m:latest .
+docker pull ghcr.io/hyperfoil/h5m:latest
```
-## Run with SQLite (Persistent Volume)
+Alternatively, build locally using Quarkus JIB (no Docker daemon required):
```bash
-docker run -d \
- --name h5m \
- -p 8080:8080 \
- -v h5m-data:/data \
- h5m:latest
+git clone https://github.com/hyperfoil/h5m.git
+cd h5m
+mvn clean package -DskipTests -Dquarkus.container-image.build=true
```
-Data persists in the `h5m-data` Docker volume between restarts.
+All examples below work with both Docker and Podman.
## Run with PostgreSQL
@@ -71,7 +33,7 @@ docker run -d \
-e QUARKUS_DATASOURCE_JDBC_URL=jdbc:postgresql://db:5432/h5m \
-e QUARKUS_DATASOURCE_USERNAME=h5m \
-e QUARKUS_DATASOURCE_PASSWORD=secret \
- h5m:latest
+ ghcr.io/hyperfoil/h5m:latest
```
## Docker Compose
@@ -81,7 +43,7 @@ A minimal Compose file with h5m and PostgreSQL:
```yaml
services:
db:
- image: postgres:16
+ image: postgres:18
environment:
POSTGRES_USER: h5m
POSTGRES_PASSWORD: secret
@@ -94,7 +56,7 @@ services:
retries: 5
h5m:
- image: h5m:latest
+ image: ghcr.io/hyperfoil/h5m:latest
ports:
- "8080:8080"
environment:
@@ -116,6 +78,20 @@ Start with:
docker compose up -d
```
+## Health Checks
+
+h5m includes SmallRye Health endpoints for readiness and liveness probes:
+
+- `GET /q/health/ready` -- returns 200 when the application is ready to serve requests
+- `GET /q/health/live` -- returns 200 when the application is alive
+
+For Testcontainers:
+
+```java
+new GenericContainer<>("ghcr.io/hyperfoil/h5m:latest")
+ .waitingFor(Wait.forHttp("/q/health/ready").forStatusCode(200))
+```
+
## With OIDC
Pass OIDC environment variables at runtime:
@@ -133,16 +109,7 @@ docker run -d \
-e QUARKUS_DATASOURCE_JDBC_URL=jdbc:postgresql://db:5432/h5m \
-e QUARKUS_DATASOURCE_USERNAME=h5m \
-e QUARKUS_DATASOURCE_PASSWORD=secret \
- h5m:latest
-```
-
-## Health Check
-
-Add a Docker health check to detect startup failures:
-
-```dockerfile
-HEALTHCHECK --interval=10s --timeout=3s --retries=5 \
- CMD curl -f http://localhost:8080/api/folder || exit 1
+ ghcr.io/hyperfoil/h5m:latest
```
## Kubernetes
@@ -166,7 +133,7 @@ spec:
spec:
containers:
- name: h5m
- image: h5m:latest
+ image: ghcr.io/hyperfoil/h5m:latest
ports:
- containerPort: 8080
env:
@@ -186,12 +153,12 @@ spec:
key: password
livenessProbe:
httpGet:
- path: /api/folder
+ path: /q/health/live
port: 8080
initialDelaySeconds: 15
readinessProbe:
httpGet:
- path: /api/folder
+ path: /q/health/ready
port: 8080
initialDelaySeconds: 10
```
diff --git a/pom.xml b/pom.xml
index fe6640d4..2303db1f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -150,6 +150,10 @@
io.quarkus
quarkus-arc
+
+ io.quarkus
+ quarkus-container-image-jib
+
io.quarkus
quarkus-hibernate-orm
@@ -194,6 +198,10 @@
io.quarkus
quarkus-rest-jsonb
+
+ io.quarkus
+ quarkus-smallrye-health
+
io.quarkus
quarkus-smallrye-openapi
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index dcdaf1b8..e58ff8b7 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -98,3 +98,10 @@ quarkus.mailer.mock=true
%test.h5m.slack.api.url=http://localhost:19876/api/chat.postMessage
h5m.slack.api.url=https://slack.com/api/chat.postMessage
+
+# Container image (JIB) — built via mvn package -Dquarkus.container-image.build=true
+quarkus.container-image.group=hyperfoil
+quarkus.container-image.name=h5m
+quarkus.container-image.registry=ghcr.io
+# Default to not building images during normal mvn package
+quarkus.container-image.build=false