Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Docker files for ARM performance improvements #282

Merged
merged 12 commits into from
Oct 13, 2024
34 changes: 16 additions & 18 deletions .github/workflows/docker-publish-arm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,44 +30,42 @@ env:
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}


jobs:
build:

runs-on: ubuntu-latest
permissions:
contents: read
packages: write
# This is used to complete the identity challenge
# with sigstore/fulcio when running outside of PRs.
attestations: write
id-token: write

steps:
# Step 1: Checkout repository
- name: Checkout repository
uses: actions/checkout@v4

# Set up BuildKit Docker container builder to be able to build
# multi-platform images and export cache
# https://github.com/docker/setup-buildx-action
# Step 2: Set up Buildx without specifying driver
# Let it use default settings to avoid the 'no remote endpoint' issue
- name: Set up Docker Buildx
uses: docker/[email protected] # v3.0.0
uses: docker/[email protected]
with:
install: true # Ensure Buildx is installed and set up properly
use: true # Use Buildx instance directly for this job

# Login against a Docker registry except on PR
# https://github.com/docker/login-action
# Step 3: Login against Docker registry except on PR
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/[email protected] # v3.0.0
uses: docker/[email protected]
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
# Step 4: Extract metadata for Docker images
- name: Extract Docker metadata
id: meta
uses: docker/[email protected] # v5.0.0
uses: docker/[email protected]
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
Expand All @@ -80,11 +78,10 @@ jobs:
flavor: |
suffix=-arm,onlatest=true

# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
# Step 5: Build and push the Docker image
- name: Build and push Docker image
id: build-and-push
uses: docker/[email protected] # v5.0.0
uses: docker/[email protected]
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
Expand All @@ -95,8 +92,9 @@ jobs:
cache-to: type=gha,mode=max
build-args: |
VERSION=${{ github.ref_name }}
COMMIT=${{ github.sha }}

COMMIT=${{ github.sha }}

# Step 6: Attest built image to prove build provenance
- name: Attest
uses: actions/attest-build-provenance@v1
id: attest
Expand Down
32 changes: 14 additions & 18 deletions .github/workflows/docker-publish-rootless-arm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,51 +24,47 @@ on:
- '.dockerignore'
- '.github/workflows'


env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}


jobs:
build-rootless:

runs-on: ubuntu-latest
permissions:
contents: read
packages: write
# This is used to complete the identity challenge
# with sigstore/fulcio when running outside of PRs.
attestations: write
id-token: write

steps:
# Step 1: Checkout repository
- name: Checkout repository
uses: actions/checkout@v4

# Set up BuildKit Docker container builder to be able to build
# multi-platform images and export cache
# https://github.com/docker/setup-buildx-action
# Step 2: Set up Buildx without specifying driver
- name: Set up Docker Buildx
uses: docker/[email protected] # v3.0.0
uses: docker/[email protected]
with:
install: true # Ensure Buildx is installed and set up properly
use: true # Use Buildx instance directly for this job

# Login against a Docker registry except on PR
# https://github.com/docker/login-action
# Step 3: Login to Docker registry except on PR
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/[email protected] # v3.0.0
uses: docker/[email protected]
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
# Step 4: Extract metadata for Docker images
- name: Extract Docker metadata
id: metadata
uses: docker/[email protected] # v5.0.0
uses: docker/[email protected]
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
Expand All @@ -80,12 +76,11 @@ jobs:
type=schedule,pattern=nightly
flavor: |
suffix=-rootless-arm,onlatest=true

# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action

# Step 5: Build and push the Docker image
- name: Build and push Docker image
id: build-and-push
uses: docker/[email protected] # v5.0.0
uses: docker/[email protected]
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
Expand All @@ -98,6 +93,7 @@ jobs:
VERSION=${{ github.ref_name }}
COMMIT=${{ github.sha }}

# Step 6: Attest built image to prove build provenance
- name: Attest
uses: actions/attest-build-provenance@v1
id: attest
Expand Down
76 changes: 49 additions & 27 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,69 +1,91 @@
# Node dependencies
FROM node:18-alpine AS frontend-dependencies
# Node dependencies stage
FROM --platform=$TARGETPLATFORM node:18-alpine AS frontend-dependencies
WORKDIR /app

# Install pnpm globally (caching layer)
RUN npm install -g pnpm

# 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
FROM node:18-alpine AS frontend-builder
WORKDIR /app
# Build Nuxt (frontend) stage
FROM --platform=$TARGETPLATFORM node:18-alpine AS frontend-builder
WORKDIR /app

# Install pnpm globally again (it can reuse the cache if not changed)
RUN npm install -g pnpm
COPY frontend .

# Copy over source files and node_modules from dependencies stage
COPY frontend .
COPY --from=frontend-dependencies /app/node_modules ./node_modules
RUN pnpm build

FROM golang:alpine AS builder-dependencies
# Go dependencies stage
FROM --platform=$TARGETPLATFORM golang:alpine AS builder-dependencies
WORKDIR /go/src/app
COPY ./backend .

# Copy go.mod and go.sum for better caching
COPY ./backend/go.mod ./backend/go.sum ./
RUN go mod download

# Build API
FROM golang:alpine AS builder
# Build API stage
FROM --platform=$TARGETPLATFORM golang:alpine AS builder
ARG BUILD_TIME
ARG COMMIT
ARG VERSION

# Install necessary build tools
RUN apk update && \
apk upgrade && \
apk add --update git build-base gcc g++
apk add --no-cache git build-base gcc g++

WORKDIR /go/src/app

# 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
RUN --mount=type=cache,target=/root/.cache/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" \
-ldflags "-s -w -X main.commit=$COMMIT -X main.buildTime=$BUILD_TIME -X main.version=$VERSION" \
-o /go/bin/api \
-v ./app/api/*.go

FROM gcr.io/distroless/java:latest

# Production Stage
FROM alpine:latest

# Production stage
FROM --platform=$TARGETPLATFORM alpine:latest
ENV HBOX_MODE=production
ENV HBOX_STORAGE_DATA=/data/
ENV HBOX_STORAGE_SQLITE_URL=/data/homebox.db?_pragma=busy_timeout=2000&_pragma=journal_mode=WAL&_fk=1

RUN apk --no-cache add ca-certificates
# Install necessary runtime dependencies
RUN apk --no-cache add ca-certificates wget

# Create application directory and copy over built Go binary
RUN mkdir /app
COPY --from=builder /go/bin/api /app

RUN chmod +x /app/api
RUN apk add --no-cache wget

# 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
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" ]

# Persist volume
VOLUME [ "/data" ]

# Entrypoint and CMD
ENTRYPOINT [ "/app/api" ]
CMD [ "/data/config.yml" ]
47 changes: 25 additions & 22 deletions Dockerfile.rootless
Original file line number Diff line number Diff line change
Expand Up @@ -7,64 +7,67 @@ RUN pnpm install --frozen-lockfile --shamefully-hoist

# Build Nuxt
FROM node:18-alpine AS frontend-builder
WORKDIR /app
RUN npm install -g pnpm
COPY frontend .
WORKDIR /app
COPY frontend ./
COPY --from=frontend-dependencies /app/node_modules ./node_modules
RUN pnpm build

# Build Go dependencies
FROM golang:alpine AS builder-dependencies
WORKDIR /go/src/app
COPY ./backend .
COPY ./backend/go.mod ./backend/go.sum ./
RUN go mod download

# Build API
FROM golang:alpine AS builder
ARG BUILD_TIME
ARG COMMIT
ARG VERSION
RUN apk update && \
apk upgrade && \
apk add --update git build-base gcc g++

RUN apk update && apk upgrade && apk add --no-cache git build-base gcc g++

WORKDIR /go/src/app
COPY ./backend .
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
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 \
-v ./app/api/*.go

FROM gcr.io/distroless/java:latest
# Use cache for Go build
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

# Production Stage
# Production stage with distroless
FROM gcr.io/distroless/static:latest

ENV HBOX_MODE=production
ENV HBOX_STORAGE_DATA=/data/
ENV HBOX_STORAGE_SQLITE_URL=/data/homebox.db?_fk=1

# Copy the binary and the (empty) /data dir and
# change the ownership to the low-privileged user
# Copy the binary and data directory, change ownership
COPY --from=builder --chown=nonroot /go/bin/api /app
COPY --from=builder --chown=nonroot /data /data

RUN apk add --no-cache wget
# Add wget to the image
# Note: If using distroless, this may not be applicable
# as distroless images do not include package managers.
# This line may be omitted if you're relying on another way to handle healthchecks.
COPY --from=alpine:latest /bin/wget /usr/bin/wget

LABEL Name=homebox Version=0.0.1
LABEL org.opencontainers.image.source="https://github.com/sysadminsmedia/homebox"
EXPOSE 7745

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" ]
VOLUME [ "/data" ]
CMD ["/usr/bin/wget", "--no-verbose", "--tries=1", "-O", "-", "http://localhost:7745/api/v1/status"]

VOLUME ["/data"]

# Drop root and run as low-privileged user
# Drop root and run as a low-privileged user
USER nonroot
ENTRYPOINT [ "/app" ]
CMD [ "/data/config.yml" ]
ENTRYPOINT ["/app"]
CMD ["/data/config.yml"]
Loading