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:
- Add the app name to the
detect-changesjob’sfor app in ...loop - Add an entry in the
buildjob’smatrix.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:
- Add to
detect-changesAPPS list - Add to
build-pushmatrix withapp,convex_url,app_url,app_name - If using Convex, add to
deploy-convexmatrix
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.opfile created -
dev:{name}script added to rootpackage.json - Health endpoint at
/api/health - Added to root
README.mdapp list - Added to root
CLAUDE.mdarchitecture tree