Add Nomad deployment configuration and CI/CD pipeline
Some checks failed
Deploy / deploy (push) Failing after 1m43s

- 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 <noreply@anthropic.com>
This commit is contained in:
2026-01-11 10:20:20 +00:00
parent 9c5b8466f6
commit 8f643c299d
5 changed files with 152 additions and 3 deletions

View File

@@ -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

111
docker.nix Normal file
View File

@@ -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";
};
}

View File

@@ -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 {

View File

@@ -1,7 +1,7 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
output: "standalone",
};
export default nextConfig;

16
src/app/metrics/route.ts Normal file
View File

@@ -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<NextResponse> {
const metrics = await metricsRegistry.metrics();
return new NextResponse(metrics, {
status: 200,
headers: {
"Content-Type": metricsRegistry.contentType,
},
});
}