Dockerfile
Rama main · réplica de lectura del repositorio original.
# syntax=docker/dockerfile:1.7
FROM node:22-bookworm-slim AS spa-builder
WORKDIR /src/frontend
COPY frontend/package.json frontend/package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci --ignore-scripts
COPY frontend/ ./
RUN npm run build-only
FROM golang:1.26-alpine AS builder
RUN apk add --no-cache gcc musl-dev
WORKDIR /src
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download
COPY cmd/ ./cmd/
COPY internal/ ./internal/
COPY --from=spa-builder /src/frontend/dist cmd/maintenant/web/dist/
ARG VERSION=dev
ARG COMMIT=unknown
ARG BUILD_DATE=unknown
ARG LICENSE_PUBLIC_KEY
ARG DEMO_MODE=false
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=1 go build \
-ldflags="-s -w \
-X main.version=${VERSION} \
-X main.commit=${COMMIT} \
-X main.buildDate=${BUILD_DATE} \
-X main.publicKeyB64=${LICENSE_PUBLIC_KEY} \
-X main.demoMode=${DEMO_MODE}" \
-o /out/maintenant \
./cmd/maintenant
FROM alpine:3.21
RUN apk upgrade --no-cache \
&& apk add --no-cache ca-certificates tzdata setpriv \
&& mkdir -p /data \
&& chown 65534:65534 /data
# Keep SQLite's temp files (statement journals, temp B-trees used by the one-time
# UUID conversion of large tables) on the data volume. The hardened runtime mounts
# /tmp as a tiny tmpfs, which SQLITE_FULL-fails the conversion; /data has real space.
ENV SQLITE_TMPDIR=/data
# Its directory also holds the licence cache and the update window, PostgreSQL or not.
ENV MAINTENANT_DB=/data/maintenant.db
# Tells the OS identity reader it must not fall back to the image's own
# /etc/os-release, which describes the container rather than the host.
ENV MAINTENANT_CONTAINER=1
COPY --from=builder --chmod=555 /out/maintenant /app/maintenant
COPY --chmod=555 docker-entrypoint.sh /docker-entrypoint.sh
ARG VERSION=dev
ARG COMMIT=unknown
ARG BUILD_DATE=unknown
LABEL org.opencontainers.image.title="maintenant" \
org.opencontainers.image.description="Monitor everything. Manage nothing." \
org.opencontainers.image.url="https://github.com/kOlapsis/maintenant" \
org.opencontainers.image.source="https://github.com/kOlapsis/maintenant" \
org.opencontainers.image.vendor="kOlapsis" \
org.opencontainers.image.licenses="Apache-2.0 AND LicenseRef-Maintenant-Commercial" \
io.modelcontextprotocol.server.name="io.github.kOlapsis/maintenant" \
org.opencontainers.image.version="${VERSION}" \
org.opencontainers.image.revision="${COMMIT}" \
org.opencontainers.image.created="${BUILD_DATE}"
EXPOSE 8080
VOLUME /data
# One image, two modes: the binary knows how to check itself. A server answers on
# the address it was configured with, an agent has no port at all and reports
# liveness through a file, so an HTTP probe here would fail every agent forever.
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD ["/app/maintenant", "healthcheck"]
# Entrypoint runs as root only to chown root-owned volume mounts and auto-detect
# the Docker socket group, then drops to uid 65534 via setpriv (see
# docker-entrypoint.sh); a build-time USER would break that chown and detection.
# nosemgrep: dockerfile.security.missing-user-entrypoint.missing-user-entrypoint
ENTRYPOINT ["/docker-entrypoint.sh"]
# nosemgrep: dockerfile.security.missing-user.missing-user
CMD ["/app/maintenant"]