diff --git a/.github/workflows/docker-publish-rootless.yaml b/.github/workflows/docker-publish-rootless.yaml index e06ae05e0..3639c64aa 100644 --- a/.github/workflows/docker-publish-rootless.yaml +++ b/.github/workflows/docker-publish-rootless.yaml @@ -31,10 +31,10 @@ jobs: build: runs-on: ubuntu-latest permissions: - contents: read # Allows access to repository contents (read-only) - packages: write # Allows pushing to GHCR - id-token: write # Allows identity token write access for authentication - attestations: write # Needed for signing and attestation (if required) + contents: read + packages: write + id-token: write + attestations: write strategy: fail-fast: false @@ -54,7 +54,6 @@ jobs: echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV branch=${{ github.event.pull_request.number || github.ref_name }} echo "BRANCH=${branch//\//-}" >> $GITHUB_ENV - - name: Docker meta id: meta @@ -75,7 +74,7 @@ jobs: with: registry: ghcr.io username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} # The GitHub token with the necessary permissions + password: ${{ secrets.GITHUB_TOKEN }} - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -92,11 +91,16 @@ jobs: id: build uses: docker/build-push-action@v6 with: + context: . # Explicitly specify the build context + file: ./Dockerfile.rootless # Explicitly specify the Dockerfile platforms: ${{ matrix.platform }} labels: ${{ steps.meta.outputs.labels }} outputs: type=image,"name=${{ env.DOCKERHUB_REPO }},${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,push=true cache-from: type=registry,ref=ghcr.io/sysadminsmedia/devcache:${{ env.PLATFORM_PAIR }}-${{ env.BRANCH }}-rootless - cache-to: type=registry,ref=ghcr.io/sysadminsmedia/devcache:${{ env.PLATFORM_PAIR}}-${{ env.BRANCH }}-rootless,mode=max + cache-to: type=registry,ref=ghcr.io/sysadminsmedia/devcache:${{ env.PLATFORM_PAIR }}-${{ env.BRANCH }}-rootless,mode=max + build-args: | + VERSION=${{ github.ref_name }} + COMMIT=${{ github.sha }} - name: Export digest run: | @@ -115,10 +119,10 @@ jobs: merge: runs-on: ubuntu-latest permissions: - contents: read # Allows access to repository contents (read-only) - packages: write # Allows pushing to GHCR! - id-token: write # Allows identity token write access for authentication - attestations: write # Needed for signing and attestation (if required) + contents: read + packages: write + id-token: write + attestations: write needs: - build diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index 5c4afac53..83f305365 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -94,6 +94,9 @@ jobs: outputs: type=image,"name=${{ env.DOCKERHUB_REPO }},${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,push=true cache-from: type=registry,ref=ghcr.io/sysadminsmedia/devcache:${{ env.PLATFORM_PAIR }}-${{ env.BRANCH }} cache-to: type=registry,ref=ghcr.io/sysadminsmedia/devcache:${{ env.PLATFORM_PAIR}}-${{ env.BRANCH }},mode=max + build-args: | + VERSION=${{ github.ref_name }} + COMMIT=${{ github.sha }} - name: Export digest run: | diff --git a/Dockerfile.rootless b/Dockerfile.rootless index f38e50a06..866bf5602 100644 --- a/Dockerfile.rootless +++ b/Dockerfile.rootless @@ -1,73 +1,96 @@ -# Node dependencies +# Node dependencies stage FROM public.ecr.aws/docker/library/node:18-alpine AS frontend-dependencies WORKDIR /app + +# Install pnpm globally (caching layer) RUN npm install -g pnpm -COPY frontend/package.json frontend/pnpm-lock.yaml ./ + +# Copy package.json and lockfile to leverage caching +COPY frontend/package.json frontend/pnpm-lock.yaml ./ RUN pnpm install --frozen-lockfile --shamefully-hoist -# Build Nuxt +# Build Nuxt (frontend) stage FROM public.ecr.aws/docker/library/node:18-alpine AS frontend-builder WORKDIR /app -COPY frontend ./ + +# Install pnpm globally again (it can reuse the cache if not changed) +RUN npm install -g pnpm + +# Copy over source files and node_modules from dependencies stage +COPY frontend . COPY --from=frontend-dependencies /app/node_modules ./node_modules RUN pnpm build -# Build Go dependencies +# Go dependencies stage FROM public.ecr.aws/docker/library/golang:alpine AS builder-dependencies WORKDIR /go/src/app -COPY ./backend/go.mod ./backend/go.sum ./ -RUN apk update && apk add --no-cache git \ - && go mod download -# Build API +# Copy go.mod and go.sum for better caching +COPY ./backend/go.mod ./backend/go.sum ./ +RUN go mod download + +# Build API stage FROM public.ecr.aws/docker/library/golang:alpine AS builder ARG BUILD_TIME ARG COMMIT ARG VERSION -RUN apk update && apk upgrade && apk add --no-cache git build-base gcc g++ \ - && addgroup -S nonroot && adduser -S nonroot -G nonroot +# Install necessary build tools +RUN apk update && \ + apk upgrade && \ + apk add --no-cache git build-base gcc g++ WORKDIR /go/src/app -COPY ./backend ./ + +# Copy Go modules (from dependencies stage) and source code +COPY --from=builder-dependencies /go/pkg/mod /go/pkg/mod +COPY ./backend . + +# Clear old public files and copy new ones from frontend build RUN rm -rf ./app/api/public COPY --from=frontend-builder /app/.output/public ./app/api/static/public -COPY --from=builder-dependencies /go/pkg/mod /go/pkg/mod -# Use cache for Go build +# Use cache for Go build artifacts RUN --mount=type=cache,target=/root/.cache/go-build \ CGO_ENABLED=0 GOOS=linux go build \ -ldflags "-s -w -X main.commit=$COMMIT -X main.buildTime=$BUILD_TIME -X main.version=$VERSION" \ - -o /go/bin/api ./app/api/*.go - -# Change ownership of files to nonroot -RUN chown -R nonroot:nonroot /go/bin/api /go/src/app + -o /go/bin/api \ + -v ./app/api/*.go -# Production stage with distroless -FROM ghcr.io/distroless/static:latest +RUN mkdir /data +# Production stage +FROM public.ecr.aws/docker/library/alpine:latest ENV HBOX_MODE=production ENV HBOX_STORAGE_DATA=/data/ -ENV HBOX_STORAGE_SQLITE_URL=/data/homebox.db?_fk=1&_time_format=sqlite +ENV HBOX_STORAGE_SQLITE_URL=/data/homebox.db?_pragma=busy_timeout=2000&_pragma=journal_mode=WAL&_fk=1&_time_format=sqlite -# Copy the binary and data directory, change ownership -COPY --from=builder /go/bin/api /app -COPY --from=builder /data /data -COPY --from=ghcr.io/rockylinux/alpine:latest /bin/wget /usr/bin/wget +# Install necessary runtime dependencies +RUN apk --no-cache add ca-certificates wget +RUN addgroup -S nonroot && adduser -S nonroot -G nonroot +# Create application directory and copy over built Go binary +RUN mkdir /app +COPY --from=builder --chown=nonroot /go/bin/api /app +COPY --from=builder --chown=nonroot /data /data +RUN chmod +x /app/api + +# Labels and configuration for the final image LABEL Name=homebox Version=0.0.1 LABEL org.opencontainers.image.source="https://github.com/sysadminsmedia/homebox" + +# Expose necessary ports for Homebox EXPOSE 7745 +WORKDIR /app -HEALTHCHECK --interval=30s \ - --timeout=5s \ - --start-period=5s \ - --retries=3 \ - CMD ["/usr/bin/wget", "--no-verbose", "--tries=1", "-O", "-", "http://localhost:7745/api/v1/status"] +# Healthcheck configuration +HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \ + CMD [ "wget", "--no-verbose", "--tries=1", "-O", "-", "http://localhost:7745/api/v1/status" ] -VOLUME ["/data"] +# Persist volume +VOLUME [ "/data" ] -# Use nonroot user +# Entrypoint and CMD USER nonroot -ENTRYPOINT ["/app"] -CMD ["/data/config.yml"] +ENTRYPOINT [ "/app/api" ] +CMD [ "/data/config.yml" ]