Development

Adding Apps

Step-by-step guide for adding a new application to the monorepo

Overview

Adding a new app to the HanseNexus monorepo involves creating the app directory, integrating with CI/CD, setting up Kubernetes manifests, and configuring secrets. Follow these steps in order.

1. Create the App Directory

Create apps/{name}/ following the standard src/ directory convention:

apps/{name}/
├── src/
│   ├── app/           # Next.js App Router pages
│   ├── components/    # App-specific components
│   │   └── features/  # Feature-based organization
│   ├── hooks/         # App-specific hooks
│   ├── lib/           # App-specific utilities
│   └── types/         # App-specific types
├── convex/            # Convex backend (if applicable)
├── e2e/               # Playwright E2E tests
├── tests/             # Vitest unit tests
├── public/            # Static assets
├── package.json
├── next.config.ts
└── tsconfig.json

2. Import Shared Packages

Add monorepo packages as dependencies in package.json:

{
  "dependencies": {
    "@hn-monorepo/ui": "workspace:*",
    "@hn-monorepo/i18n": "workspace:*",
    "@hn-monorepo/config": "workspace:*",
    "@hn-monorepo/monitoring": "workspace:*",
    "@hn-monorepo/shared": "workspace:*"
  }
}

3. Add Environment Variables to turbo.json

If the app uses environment variables not already listed, add them to turbo.json:

{
  "globalEnv": [
    "NEXT_PUBLIC_APP_URL",
    "NEXT_PUBLIC_CONVEX_URL",
    "NEXT_PUBLIC_APP_NAME"
  ]
}

4. Add to CI Matrix

Update .github/workflows/ci.yml:

  1. Add the app name to the detect-changes job’s for app in ... loop
  2. Add an entry in the build job’s matrix.include:
    - app: my-app
      url: https://my-app.hansenexus.dev
      enabled: true

5. Create the Dockerfile Build Arg Support

The shared Dockerfile at the repo root uses APP_NAME as a build arg. Ensure your app works with the multi-stage build:

docker buildx build --build-arg APP_NAME=my-app \
  --build-arg NEXT_PUBLIC_CONVEX_URL=https://convex-my-app.hansenexus.dev \
  --build-arg NEXT_PUBLIC_APP_URL=https://my-app.hansenexus.dev \
  --build-arg NEXT_PUBLIC_APP_NAME="My App" .

6. Add Kubernetes Manifests

Create the following files in k8s/apps/{name}/:

kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml
  - service.yaml
  - ingress.yaml
  - serviceaccount.yaml
  - onepassworditem.yaml

deployment.yaml

Include the standard configuration:

  • Rolling update strategy (maxUnavailable: 0, maxSurge: 1)
  • Liveness and readiness probes on /api/health
  • imagePullPolicy: Always
  • ServiceAccount reference with automountServiceAccountToken: false
  • Environment variables from 1Password secret
  • OTel instrumentation annotation

service.yaml

apiVersion: v1
kind: Service
metadata:
  name: {name}
  namespace: hn-apps
spec:
  selector:
    app: {name}
  ports:
    - port: 80
      targetPort: 3000

ingress.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {name}
  namespace: hn-apps
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts:
        - {name}.hansenexus.dev
      secretName: {name}-tls
  rules:
    - host: {name}.hansenexus.dev
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: {name}
                port:
                  number: 80

serviceaccount.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: {name}
  namespace: hn-apps
automountServiceAccountToken: false

onepassworditem.yaml

apiVersion: onepassword.com/v1
kind: OnePasswordItem
metadata:
  name: {name}-secrets
  namespace: hn-apps
spec:
  itemPath: "vaults/k3/items/{name}-prod"

Add to root kustomization

Add apps/{name}/ to the root k8s/kustomization.yaml resources list.

7. Add to Deploy Pipeline

Update .github/workflows/deploy.yml:

  1. Add to detect-changes APPS list
  2. Add to build-push matrix with app, convex_url, app_url, app_name
  3. If using Convex, add to deploy-convex matrix

8. Create 1Password Vault Item

Create an item in the k3 vault named {name}-prod with all required secret fields. Field names must use SCREAMING_SNAKE_CASE and match exactly what the app expects.

9. Add DNS Record

Point {name}.hansenexus.dev to the cluster public IP 91.99.1.144 (A record).

10. Create .env.op File

Create apps/{name}/.env.op with op:// references for secrets and literal values for non-secrets:

# apps/{name}/.env.op
AUTH_SECRET=op://k3/{name}-prod/AUTH_SECRET
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_APP_NAME=My App

Add a corresponding dev:{name} script to the root package.json.

11. Add Health Endpoint

Create src/app/api/health/route.ts:

import { NextResponse } from "next/server";

export function GET() {
  return NextResponse.json({
    status: "ok",
    timestamp: Date.now(),
  });
}

This endpoint is used by Kubernetes liveness and readiness probes.

Checklist

  • App directory created with src/ convention
  • Shared packages imported
  • Environment variables added to turbo.json
  • Added to CI matrix in ci.yml
  • Kubernetes manifests created in k8s/apps/{name}/
  • Added to deploy pipeline in deploy.yml
  • 1Password vault item created
  • DNS record added
  • .env.op file created
  • dev:{name} script added to root package.json
  • Health endpoint at /api/health
  • Added to root README.md app list
  • Added to root CLAUDE.md architecture tree
HanseNexus 2026