# Frontend: built first, it is the part that changes most often FROM node:22-slim AS frontend WORKDIR /build # Dependencies in their own layer, so a source-only change does not reinstall them COPY frontend/package.json frontend/package-lock.json ./ RUN npm ci COPY frontend/ ./ # The checks run here rather than in a separate CI job: the build then fails on # the runner for the same reason it would fail locally, and there is only one # environment to keep working. RUN npx eslint . --max-warnings=0 RUN npm run build # type-check + vite build # Backend: compiled against the sqlx offline data, so no database is needed here FROM rust:1.97-slim AS backend WORKDIR /build ENV SQLX_OFFLINE=true # Traceability: `build.rs` falls back on this when there is no git repository ARG GIT_HASH=unknown ENV GIT_HASH=${GIT_HASH} # Warm the dependency layer with a dummy main, so editing src/ does not rebuild # every crate we depend on COPY Cargo.toml Cargo.lock build.rs ./ RUN mkdir src && echo 'fn main() {}' > src/main.rs \ && cargo build --release \ && rm -rf src COPY .sqlx/ .sqlx/ COPY src/ src/ # cargo skips a rebuild when only mtime changed: force it for our own crate RUN touch src/main.rs \ && cargo build --release --locked \ && cargo test --release --locked # Runtime FROM debian:trixie-slim LABEL org.opencontainers.image.title="vm-selector" # virsh is how the app talks to libvirt. openssh-client is only needed if you # point vm.uri at a qemu+ssh:// uri instead of the mounted socket; curl serves # the healthcheck. RUN apt-get update \ && apt-get install -y --no-install-recommends libvirt-clients openssh-client curl \ && rm -rf /var/lib/apt/lists/* # The migrations travel with the image, so the stack needs nothing from the # repository: the `migrate` service runs this same image with another entrypoint. COPY --from=ghcr.io/amacneil/dbmate:2 /usr/local/bin/dbmate /usr/local/bin/dbmate COPY db/migrations/ /app/db/migrations/ # Unprivileged, but it must land in the host's libvirt group to reach the # socket: `group_add` in the compose file does that at run time. RUN useradd --system --create-home --uid 10001 app WORKDIR /app COPY --from=backend /build/target/release/app-template /usr/local/bin/vm-selector COPY --from=frontend /build/dist/ /app/frontend/ COPY docker/config.yml /etc/app-template/config.yml USER app EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD curl -fsS http://localhost:3000/api/version || exit 1 # No shell: signals reach the binary directly, so the graceful shutdown works ENTRYPOINT ["/usr/local/bin/vm-selector"]