From 8f643c299d1e45e5818937f0485b2160cc1d312d Mon Sep 17 00:00:00 2001 From: Petru Paler Date: Sun, 11 Jan 2026 10:20:20 +0000 Subject: [PATCH] Add Nomad deployment configuration and CI/CD pipeline - Add docker.nix for Nix-based Docker image builds - Update flake.nix with dockerImage package output - Add output: standalone to next.config.ts for production builds - Add /metrics endpoint for Prometheus scraping - Add Gitea Actions workflow calling shared deploy-nomad.yaml Co-Authored-By: Claude Opus 4.5 --- .gitea/workflows/deploy.yaml | 16 +++++ docker.nix | 111 +++++++++++++++++++++++++++++++++++ flake.nix | 10 +++- next.config.ts | 2 +- src/app/metrics/route.ts | 16 +++++ 5 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 .gitea/workflows/deploy.yaml create mode 100644 docker.nix create mode 100644 src/app/metrics/route.ts diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml new file mode 100644 index 0000000..bb49ad4 --- /dev/null +++ b/.gitea/workflows/deploy.yaml @@ -0,0 +1,16 @@ +# ABOUTME: Gitea Actions workflow for deploying PhaseFlow to Nomad. +# ABOUTME: Builds Nix Docker image and deploys using shared workflow. + +name: Deploy + +on: + push: + branches: [main] + workflow_dispatch: + +jobs: + deploy: + uses: alo/alo-cluster/.gitea/workflows/deploy-nomad.yaml@master + with: + service_name: phaseflow + secrets: inherit diff --git a/docker.nix b/docker.nix new file mode 100644 index 0000000..36e9177 --- /dev/null +++ b/docker.nix @@ -0,0 +1,111 @@ +# ABOUTME: Nix expression for building PhaseFlow Docker image. +# ABOUTME: Creates standalone Next.js production bundle with minimal dependencies. +{ pkgs }: + +let + src = pkgs.lib.cleanSource ./.; + + # Build the Next.js application using pnpm + # Note: This builds with network access. For fully reproducible builds, + # consider using pnpm.fetchDeps or dream2nix in the future. + phaseflow = pkgs.stdenv.mkDerivation { + pname = "phaseflow"; + version = "0.1.0"; + inherit src; + + nativeBuildInputs = with pkgs; [ + nodejs_24 + pnpm + cacert + ]; + + # Allow network access for pnpm install + __noChroot = true; + + # Enable network during build (requires trusted-users in nix.conf) + # Alternative: use sandbox = false for this derivation + impureEnvVars = pkgs.lib.fetchers.proxyImpureEnvVars ++ [ + "HOME" + "npm_config_cache" + ]; + + buildPhase = '' + export HOME=$TMPDIR + export NEXT_TELEMETRY_DISABLED=1 + + # Provide dummy env vars for build (actual values injected at runtime) + export RESEND_API_KEY="re_build_placeholder" + export ENCRYPTION_KEY="build_placeholder_32_chars_long!" + export CRON_SECRET="build_placeholder_secret" + export POCKETBASE_URL="http://localhost:8090" + export APP_URL="https://phaseflow.v.paler.net" + + # Install dependencies + pnpm install --frozen-lockfile + + # Build the Next.js app with standalone output + pnpm build + ''; + + # Disable broken symlink check - pnpm creates internal symlinks we don't need + dontCheckForBrokenSymlinks = true; + + installPhase = '' + mkdir -p $out + + # Copy standalone server (self-contained with minimal node_modules) + cp -r .next/standalone/* $out/ + + # Copy static assets (Next.js standalone requires these separately) + mkdir -p $out/.next + cp -r .next/static $out/.next/static + + # Copy public assets + if [ -d public ]; then + cp -r public $out/public + fi + ''; + }; +in +pkgs.dockerTools.buildImage { + name = "gitea.v.paler.net/alo/phaseflow"; + tag = "latest"; + + copyToRoot = pkgs.buildEnv { + name = "phaseflow-env"; + paths = with pkgs; [ + # System utilities + busybox + bash + + # Node.js runtime + nodejs_24 + + # Docker filesystem helpers + dockerTools.usrBinEnv + dockerTools.binSh + dockerTools.fakeNss + dockerTools.caCertificates + ]; + }; + + # Copy the built application + extraCommands = '' + mkdir -p -m 1777 tmp + mkdir -p app + cp -r ${phaseflow}/* app/ + ''; + + config = { + Env = [ + "NODE_ENV=production" + "PORT=3000" + "HOSTNAME=0.0.0.0" + ]; + ExposedPorts = { + "3000/tcp" = {}; + }; + Cmd = [ "${pkgs.nodejs_24}/bin/node" "/app/server.js" ]; + WorkingDir = "/app"; + }; +} diff --git a/flake.nix b/flake.nix index 0cac1de..e7ec633 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,5 @@ -# ABOUTME: Nix flake for PhaseFlow development environment. -# ABOUTME: Provides Node.js 24, pnpm, turbo, lefthook, and Ralph sandbox shell. +# ABOUTME: Nix flake for PhaseFlow development environment and Docker build. +# ABOUTME: Provides Node.js 24, pnpm, turbo, lefthook, and Docker image output. { inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; @@ -16,6 +16,12 @@ pocketbase ]; in { + # Docker image for production deployment + packages.${system} = { + dockerImage = import ./docker.nix { inherit pkgs; }; + default = import ./docker.nix { inherit pkgs; }; + }; + devShells.${system} = { # Default development shell with all tools default = pkgs.mkShell { 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; diff --git a/src/app/metrics/route.ts b/src/app/metrics/route.ts new file mode 100644 index 0000000..a563e55 --- /dev/null +++ b/src/app/metrics/route.ts @@ -0,0 +1,16 @@ +// ABOUTME: Prometheus metrics endpoint at /metrics for standard scraping path. +// ABOUTME: Re-exports the same metrics as /api/metrics for Prometheus autodiscovery. + +import { NextResponse } from "next/server"; +import { metricsRegistry } from "@/lib/metrics"; + +export async function GET(): Promise { + const metrics = await metricsRegistry.metrics(); + + return new NextResponse(metrics, { + status: 200, + headers: { + "Content-Type": metricsRegistry.contentType, + }, + }); +}