Add Docker image build to CI pipeline

Multi-stage Dockerfile using hotio/base:alpinevpn with s6-overlay.
Docker job runs after backend, unit-tests, and frontend pass.
Builds linux/amd64 and linux/arm64 images, pushes to GHCR on
non-PR events. Supports manual dispatch with optional version tag.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
nitrobass24 2026-04-06 21:10:14 -05:00
parent 67274ab7b5
commit e09567659a
12 changed files with 187 additions and 2 deletions

12
.dockerignore Normal file
View file

@ -0,0 +1,12 @@
.git
.github
.vscode
.devcontainer
_output
_temp
_tests
_artifacts
node_modules
Logo
*.md
!LICENSE

View file

@ -1,8 +1,8 @@
name: Build & Test
name: Build, Test & Docker
on:
push:
branches: [develop, master]
branches: [develop, master, feature/**]
paths-ignore:
- 'src/NzbDrone.Core/Localization/Core/**'
- 'src/Prowlarr.Api.*/openapi.json'
@ -11,12 +11,20 @@ on:
paths-ignore:
- 'src/NzbDrone.Core/Localization/Core/**'
- 'src/Prowlarr.Api.*/openapi.json'
workflow_dispatch:
inputs:
version:
description: "Version tag (e.g. 2.3.3.1234)"
required: false
type: string
env:
DOTNET_VERSION: '8.0.x'
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
NODE_VERSION: '20'
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
backend:
@ -138,3 +146,67 @@ jobs:
- name: Build
run: yarn run build --env production
docker:
name: Build & Push Docker Image
runs-on: ubuntu-latest
needs: [backend, unit-tests, frontend]
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Determine version
id: version
env:
INPUT_VERSION: ${{ inputs.version }}
run: |
if [ -n "$INPUT_VERSION" ]; then
echo "version=$INPUT_VERSION" >> "$GITHUB_OUTPUT"
else
echo "version=$(git describe --tags 2>/dev/null || echo "dev-${GITHUB_SHA::8}")" >> "$GITHUB_OUTPUT"
fi
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=sha,prefix=
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/develop' }}
type=raw,value=${{ steps.version.outputs.version }},enable=${{ inputs.version != '' }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
VERSION=${{ steps.version.outputs.version }}
VERSION_BRANCH=${{ github.ref_name }}
cache-from: type=gha
cache-to: type=gha,mode=max

74
Dockerfile Normal file
View file

@ -0,0 +1,74 @@
# Stage 1: Build backend
FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS backend
WORKDIR /repo
ARG TARGETPLATFORM
RUN case "${TARGETPLATFORM}" in \
"linux/arm64") echo "linux-musl-arm64" > /tmp/rid ;; \
"linux/arm/v7") echo "linux-musl-arm" > /tmp/rid ;; \
*) echo "linux-musl-x64" > /tmp/rid ;; \
esac
# Copy solution and project files first for layer caching on restore
COPY src/Prowlarr.sln src/Directory.Build.props src/Directory.Build.targets src/NuGet.config src/stylecop.json src/
COPY src/Targets/ src/Targets/
COPY src/Libraries/ src/Libraries/
COPY src/NzbDrone/Prowlarr.csproj src/NzbDrone/
COPY src/NzbDrone.Common/Prowlarr.Common.csproj src/NzbDrone.Common/
COPY src/NzbDrone.Console/Prowlarr.Console.csproj src/NzbDrone.Console/
COPY src/NzbDrone.Core/Prowlarr.Core.csproj src/NzbDrone.Core/
COPY src/NzbDrone.Host/Prowlarr.Host.csproj src/NzbDrone.Host/
COPY src/NzbDrone.Mono/Prowlarr.Mono.csproj src/NzbDrone.Mono/
COPY src/NzbDrone.SignalR/Prowlarr.SignalR.csproj src/NzbDrone.SignalR/
COPY src/NzbDrone.Update/Prowlarr.Update.csproj src/NzbDrone.Update/
COPY src/NzbDrone.Windows/Prowlarr.Windows.csproj src/NzbDrone.Windows/
COPY src/Prowlarr.Api.V1/Prowlarr.Api.V1.csproj src/Prowlarr.Api.V1/
COPY src/Prowlarr.Http/Prowlarr.Http.csproj src/Prowlarr.Http/
COPY src/ServiceHelpers/ServiceInstall/ServiceInstall.csproj src/ServiceHelpers/ServiceInstall/
COPY src/ServiceHelpers/ServiceUninstall/ServiceUninstall.csproj src/ServiceHelpers/ServiceUninstall/
RUN dotnet restore src/Prowlarr.sln -r "$(cat /tmp/rid)" -p:SelfContained=true
# Copy remaining source and build
COPY src/ src/
COPY LICENSE LICENSE
RUN dotnet publish src/NzbDrone/Prowlarr.csproj \
-c Release \
-r "$(cat /tmp/rid)" \
--self-contained \
--no-restore \
-o /build/bin && \
rm -rf /build/bin/Prowlarr.Update /build/bin/Prowlarr.Windows.* \
/build/bin/ServiceInstall.* /build/bin/ServiceUninstall.*
# Stage 2: Build frontend
FROM node:20-alpine AS frontend
WORKDIR /repo
COPY package.json yarn.lock .yarnrc ./
RUN yarn install --frozen-lockfile --network-timeout 120000
COPY frontend/ frontend/
COPY tsconfig.json ./
RUN yarn build --env production
# Stage 3: Runtime on hotio base
FROM ghcr.io/hotio/base:alpinevpn
EXPOSE 9696
ENV WEBUI_PORTS="9696/tcp"
RUN apk add --no-cache libintl sqlite-libs icu-libs
COPY --from=backend /build/bin ${APP_DIR}/bin
COPY --from=frontend /repo/_output/UI ${APP_DIR}/bin/UI
ARG VERSION
ARG VERSION_BRANCH=develop
RUN echo -e "PackageVersion=${VERSION:-local}\nPackageAuthor=[nitrobass24](https://github.com/nitrobass24)\nUpdateMethod=Docker\nBranch=${VERSION_BRANCH}" \
> "${APP_DIR}/package_info" && \
chmod -R u=rwX,go=rX "${APP_DIR}"
COPY root/ /
RUN find /etc/s6-overlay/s6-rc.d -name "run*" -execdir chmod +x {} +

View file

@ -0,0 +1,17 @@
#!/command/with-contenv bash
# shellcheck shell=bash
# shellcheck source=/dev/null
source /etc/s6-overlay/scripts/bash-functions
set -e
umask "${UMASK}"
echo "
----------------------------------------------------------------------
ENVIRONMENT APP
----------------------------------------------------------------------
WEBUI_PORTS=${WEBUI_PORTS}
----------------------------------------------------------------------
"

View file

@ -0,0 +1 @@
oneshot

View file

@ -0,0 +1 @@
/etc/s6-overlay/s6-rc.d/init-setup-app/run

View file

@ -0,0 +1,7 @@
#!/command/with-contenv bash
# shellcheck shell=bash
umask "${UMASK}"
cd "${APP_DIR}/bin" || exit 1
exec s6-setuidgid hotio "${APP_DIR}/bin/Prowlarr" --nobrowser --data="${CONFIG_DIR}"

View file

@ -0,0 +1 @@
longrun