From 3eeac0fe10a812c7f5e8f98f1f1d5588c190e2c9 Mon Sep 17 00:00:00 2001 From: Josh Wright Date: Sun, 19 Apr 2026 10:04:22 -0400 Subject: [PATCH] Add Docker build and Gitea Actions CI for deployment Multi-stage Alpine Dockerfile producing a standalone Next.js image with the better-sqlite3 native addon included. Gitea workflow builds on push to main and on v* tags, pushing to the Gitea registry using the REGISTRY_URL variable and REGISTRY_TOKEN secret. Compose file mounts ./data so alerts.db and settings.json persist across restarts. Co-Authored-By: Claude Opus 4.7 --- .dockerignore | 27 +++++++++++++++++++++ .gitea/workflows/build.yml | 48 ++++++++++++++++++++++++++++++++++++++ Dockerfile | 39 +++++++++++++++++++++++++++++++ docker-compose.yml | 24 +++++++++++++++++++ next.config.ts | 2 +- 5 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 .gitea/workflows/build.yml create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..387d0fa --- /dev/null +++ b/.dockerignore @@ -0,0 +1,27 @@ +.git +.gitea +.github +.claude +.vscode +.idea + +node_modules +.next +out +build +coverage + +data +.env* + +README.md +*.md +!package.json +Dockerfile +docker-compose*.yml +.dockerignore + +*.log +.DS_Store +*.tsbuildinfo +next-env.d.ts diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..cc0cb9b --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,48 @@ +name: Build and Push + +on: + push: + branches: [main] + tags: ["v*"] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Derive image reference + id: img + run: | + echo "name=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT + echo "sha_short=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT + + - uses: docker/setup-buildx-action@v3 + + - uses: docker/login-action@v3 + with: + registry: ${{ vars.REGISTRY_URL }} + username: ${{ github.actor }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: Compute tags + id: tags + run: | + IMAGE="${{ vars.REGISTRY_URL }}/${{ steps.img.outputs.name }}" + TAGS="${IMAGE}:${{ steps.img.outputs.sha_short }}" + if [ "${{ github.ref }}" = "refs/heads/main" ]; then + TAGS="${TAGS},${IMAGE}:latest" + fi + if [[ "${{ github.ref }}" == refs/tags/v* ]]; then + TAGS="${TAGS},${IMAGE}:${GITHUB_REF_NAME}" + fi + echo "tags=${TAGS}" >> $GITHUB_OUTPUT + + - uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ${{ steps.tags.outputs.tags }} + cache-from: type=registry,ref=${{ vars.REGISTRY_URL }}/${{ steps.img.outputs.name }}:buildcache + cache-to: type=registry,ref=${{ vars.REGISTRY_URL }}/${{ steps.img.outputs.name }}:buildcache,mode=max diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..47be24b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +# syntax=docker/dockerfile:1.7 +ARG NODE_VERSION=22-alpine + +# ── deps: install node_modules (with native build tools for better-sqlite3) ── +FROM node:${NODE_VERSION} AS deps +RUN apk add --no-cache libc6-compat python3 make g++ +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci + +# ── builder: produce the standalone Next.js bundle ── +FROM node:${NODE_VERSION} AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . +ENV NEXT_TELEMETRY_DISABLED=1 +RUN npm run build + +# ── runner: minimal runtime image ── +FROM node:${NODE_VERSION} AS runner +WORKDIR /app +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +ENV PORT=3000 +ENV HOSTNAME=0.0.0.0 + +RUN addgroup --system --gid 1001 nodejs \ + && adduser --system --uid 1001 nextjs + +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +RUN mkdir -p /app/data && chown nextjs:nodejs /app/data + +USER nextjs +EXPOSE 3000 +VOLUME ["/app/data"] +CMD ["node", "server.js"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..0ca7b4c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +services: + oversnitch: + image: ${REGISTRY_URL:?set REGISTRY_URL to your Gitea host, e.g. gitea.example.com}/${IMAGE_REPO:-josh/oversnitch}:${IMAGE_TAG:-latest} + container_name: oversnitch + restart: unless-stopped + ports: + - "${HOST_PORT:-3000}:3000" + volumes: + # Persists alerts.db and settings.json across restarts + - ./data:/app/data + environment: + - NODE_ENV=production + # All service URLs/API keys can be configured via the Settings UI after + # first boot. Uncomment below to provide them via env instead. + # - SEERR_URL= + # - SEERR_API= + # - RADARR_URL= + # - RADARR_API= + # - SONARR_URL= + # - SONARR_API= + # - TAUTULLI_URL= + # - TAUTULLI_API= + # - DISCORD_WEBHOOK= + # - NODE_TLS_REJECT_UNAUTHORIZED=0 diff --git a/next.config.ts b/next.config.ts index e9ffa30..68a6c64 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + output: "standalone", }; export default nextConfig;