Cuando el volumen named 'shot-crafter-data' es nuevo, Docker lo monta
con owner root:root. La aplicacion corre como UID 1001 y no puede
escribir -> H2 falla al crear shots.mv.db -> BootstrapAdmin no crea
el admin -> no se puede login.
Quitar 'USER 1001' del Dockerfile, agregar entrypoint.sh que:
1. chown -R 1001:1001 /work/data (la app va a poder escribir)
2. chmod 0755 /work
3. setpriv --reuid=1001 --regid=1001 --init-groups -- /work/application
(drop a no-root antes del exec)
Resultado: el contenedor arranca como root el primer instante, hace
el fix de permisos una sola vez, y desde ese momento la app corre
como 1001. Docker defaults USER a root cuando no hay directiva USER.
The native binary is built against glibc 2.35 (Ubuntu jammy in CI) but
quarkus-micro-image:2.0 ships glibc 2.34 (UBI 9), hence the
'GLIBC_2.35 not found' at runtime. Switching to ubuntu:22.04 as the
runtime base matches glibc exactly and lets us stop fighting with
musl / static binaries.
Dockerfile:
- FROM ubuntu:22.04 (was quarkus-micro-image:2.0)
- install curl + ca-certificates via apt (apt + bash are present, so
chmod/echo securerandom run in-line again)
- single-stage: no more curl-builder multi-stage
- useradd UID 1001 (matches the in-container USER)
CI:
- drop binary-type=STATIC and --libc=musl (binary is dynamic again)
- drop the rm -f target/*-runner (cache invalidate)
- 'Stage binary for Docker' is now just 'cp'
- verify step is informational only
Image is ~5 MB larger than quarkus-micro-image but the runtime now
matches the build glibc, so the container starts cleanly.
ldd reports library paths as /lib64/... (the legacy short path).
quarkus-micro-image:2.0 has /lib64 as a symlink to /usr/lib64, so
COPY --from=curl-builder /out/ / collides when trying to write into
/lib64 (cannot copy to non-directory).
The fix: in the curl-builder stage, translate /lib64 -> /usr/lib64
(and /lib -> /usr/lib) before installing each library, so the final
image gets libs under /usr/lib64 and doesn't touch the /lib64 symlink.
Drop the LXC deploy step. Pipeline now stops at publishing the image
to the Gitea registry; deployment is handled out of band.
Restored:
- Dockerfile (multi-stage: curl-builder + quarkus-micro-image:2.0,
generic via build-output/*-runner wildcard, COPY --chown=1001:1001)
- compose.yaml (one-shot install of the published image)
- .dockerignore (excludes build-output/)
CI workflow:
- Installs docker-buildx (needed for COPY --chown)
- Uses docker buildx build
- chmod 775 and echo securerandom happen in the 'Stage binary for Docker'
step; the final image has no RUN commands
- Tags :latest and :<short-sha>, pushes with retry
No deploy step. Pull the image with docker compose / run it manually.
Switch the runtime from a Docker image to a systemd service running the
native binary on the LXC host. The CI still uses Docker for the build
environment (maven:3.9.6-eclipse-temurin-21), but stops at producing the
static native binary.
Pipeline changes:
- Drop docker.io, docker-buildx, docker buildx, docker push, registry.
- Drop Dockerfile, compose.yaml, .dockerignore (no longer needed).
- Build native binary in CI container, SCP to LXC, run deploy script.
- Deploy script stops the service, swaps the binary, starts it, hits
/q/health/live to verify.
LXC one-time setup (manual, run on the host):
- useradd runner (UID 1001)
- mkdir /opt/shot-crafter-calculator/{data,keys,deploy}
- copy RSA JWT keys into keys/
- install /etc/systemd/system/shot-crafter-calculator.service
- install /usr/local/bin/deploy-shot-crafter-calculator.sh
- useradd deployer + ssh keypair for the CI
- store DEPLOY_SSH_KEY secret in Gitea
Bootstrap the first deploy manually with scp + ssh before relying on CI.
Removed the RUN chmod/chown/echo steps from the Dockerfile. The Quarkus
micro image 2.0 ships without /bin/sh and /usr/bin/sh, so any RUN
instruction fails. The chmod and securerandom.source append now happen
in the CI 'Stage binary for Docker' step, and COPY --chown=1001:1001
takes ownership of the binary in the image.
COPY --chown requires BuildKit, so:
- Install docker-buildx in the CI
- Switch docker build -> docker buildx build
The final image stays minimal (no shell, no microdnf, no extra packages).
The Quarkus micro image 2.0 no longer ships /bin/sh (only /usr/bin/sh).
Docker's default shell is /bin/sh, so RUN commands fail with
'exec /bin/sh: no such file or directory'. Set SHELL explicitly to
/usr/bin/sh to keep the small final image while letting Docker run
RUN commands.
The 2.0 rebuild of quarkus-micro-image removed microdnf to slim the image,
so the inline 'microdnf install curl-minimal' step now fails with
'command not found'.
Build curl-minimal in a ubi9/ubi-minimal builder stage, then copy only the
curl binary + its runtime shared libs + CA bundle into the final
quarkus-micro-image layer. Final image stays slim and the docker-compose
healthcheck keeps working.
- pom.xml: add quarkus-smallrye-health dependency.
Exposes /q/health/live and /q/health/ready endpoints (liveness
and readiness probes for Quarkus apps).
- Dockerfile: install curl-minimal in the microdnf layer so the
container has a real HTTP client. quarkus-micro-image is based
on UBI 9 minimal and doesn't ship with curl by default.
- compose.yaml: healthcheck now hits GET /q/health/live with
curl -f instead of the previous kill -0 1 (which only proved
the process was alive, not that the HTTP server was responding).
The next CI run will rebuild the native binary with the health
extension baked in; old images pulled from :latest will keep
working since this is additive.
- Add .env.example with all configurable variables documented
(DB URL, HTTP port/host, cookie, JWT issuer/keys, log level)
- Remove hardcoded -Dquarkus.http.host from Dockerfile ENTRYPOINT
(application.properties already sets the default; env vars
can now override it at runtime without conflicting with -D flags)
All env vars follow Quarkus's auto-binding convention:
property.key → PROPERTY_KEY (uppercase)
Most useful for production:
- QUARKUS_DATASOURCE_JDBC_URL: DB file path
- QUARKUS_HTTP_PORT: HTTP port
- QUARKUS_HTTP_HOST: bind interface
- APP_AUTH_COOKIE_SECURE: enable Secure flag behind HTTPS
- MP_JWT_VERIFY_PUBLICKEY_LOCATION: externalize RSA keys
The .dockerignore has 'target' which excludes the target/
directory from the docker build context. The Dockerfile was
COPYing the binary from target/, so docker build failed with
'file not found in build context or excluded by .dockerignore'.
Fix: copy the binary to build-output/ (a non-excluded path)
before docker build, and update the Dockerfile to copy from
build-output/.
- Add 'Stage binary for Docker' step that does:
mkdir -p build-output
cp target/shot-crafter-calculator-1.0.0-runner build-output/
- Dockerfile COPY now reads build-output/shot-crafter-calculator-1.0.0-runner
- build-output/ is not in .dockerignore -> only the binary
(~117MB) ships in the build context, not the whole target/
tree (~200MB with classes, generated-sources, node binaries, etc.)
Adds:
- Dockerfile based on quarkus-micro-image:2.0 (~50MB base)
Runs the native binary as non-root user 1001, exposes 8080
- .dockerignore to exclude build artifacts
- Gitea Actions workflow with 3 parallel jobs:
- build-jvm: standard JAR (mvn package)
- build-native: GraalVM native binary (mvn package -Pnative)
- docker-native: takes the native binary artifact, builds
the container image and pushes to
gitea.danielarroyo.cl/proyectos/shot-crafter-calculator
with tags 'latest' and short SHA
Triggers: push to main and PRs (docker job only on push).
Required secrets: GITEA_USERNAME, GITEA_TOKEN (write:packages).