scaffold: kind+Tilt cluster, api/ui/docs stubs, green at nvi.local.ar
This commit is contained in:
12
ctrl/Dockerfile.api
Normal file
12
ctrl/Dockerfile.api
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM python:3.13-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN pip install --no-cache-dir uv
|
||||
|
||||
COPY pyproject.toml uv.lock* ./
|
||||
RUN uv sync --no-dev --no-install-project --frozen 2>/dev/null || uv sync --no-dev --no-install-project
|
||||
|
||||
COPY api/ api/
|
||||
|
||||
CMD ["uv", "run", "uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
|
||||
3
ctrl/Dockerfile.docs
Normal file
3
ctrl/Dockerfile.docs
Normal file
@@ -0,0 +1,3 @@
|
||||
FROM nginx:alpine
|
||||
COPY docs/ /usr/share/nginx/html/
|
||||
COPY ctrl/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
22
ctrl/Dockerfile.ui
Normal file
22
ctrl/Dockerfile.ui
Normal file
@@ -0,0 +1,22 @@
|
||||
FROM node:22-slim AS build
|
||||
|
||||
# Pin pnpm to v9 — v10 treats "ignored build scripts" (esbuild, vue-demi) as
|
||||
# fatal under CI=true. Allowlisting them belongs in spr's framework
|
||||
# package.json; until that lands, v9's lenient behavior keeps us building.
|
||||
RUN corepack enable && corepack prepare pnpm@9 --activate
|
||||
|
||||
WORKDIR /ui
|
||||
COPY ui/framework/ framework/
|
||||
COPY ui/app/ app/
|
||||
|
||||
ENV CI=true
|
||||
|
||||
WORKDIR /ui/framework
|
||||
RUN pnpm install
|
||||
|
||||
WORKDIR /ui/app
|
||||
RUN pnpm install && pnpm run build
|
||||
|
||||
FROM nginx:alpine
|
||||
COPY --from=build /ui/app/dist /usr/share/nginx/html
|
||||
COPY ctrl/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
80
ctrl/Tiltfile
Normal file
80
ctrl/Tiltfile
Normal file
@@ -0,0 +1,80 @@
|
||||
# NVI — Tilt development environment
|
||||
# Usage: cd ctrl && tilt up
|
||||
# Cluster: kind (name: nvi — create via ./kind-config.sh)
|
||||
# Entry points (via system Caddy + dnsmasq; kind NodePort is 30060→host 8060):
|
||||
# http://nvi.local.ar — UI
|
||||
# http://api.nvi.local.ar — API
|
||||
# http://docs.nvi.local.ar — Docs
|
||||
|
||||
allow_k8s_contexts('kind-nvi')
|
||||
|
||||
# Hard guard: Tilt snapshots the kubectl context at startup (before parsing
|
||||
# this file), so we can't switch it from here. Prefer `tilt up --context
|
||||
# kind-nvi` to bypass the shell's global context entirely.
|
||||
if k8s_context() != 'kind-nvi':
|
||||
fail("Wrong kubectl context: '%s'. Run with: tilt up --context kind-nvi" % k8s_context())
|
||||
|
||||
local('kubectl --context kind-nvi create namespace nvi --dry-run=client -o yaml | kubectl --context kind-nvi apply -f -')
|
||||
|
||||
k8s_yaml(kustomize('k8s/overlays/dev'))
|
||||
|
||||
# --- Images ---
|
||||
|
||||
# FastAPI — Plan / Analyses / Tools / runtime
|
||||
docker_build(
|
||||
'nvi-api',
|
||||
context='..',
|
||||
dockerfile='Dockerfile.api',
|
||||
ignore=['.git', 'def', 'ui', '.claude', 'tests', '.venv', 'node_modules', '.data', 'docs'],
|
||||
live_update=[
|
||||
sync('../api', '/app/api'),
|
||||
],
|
||||
)
|
||||
|
||||
# Vue UI — context is project root so the framework symlink resolves
|
||||
docker_build(
|
||||
'nvi-ui',
|
||||
context='..',
|
||||
dockerfile='Dockerfile.ui',
|
||||
live_update=[
|
||||
sync('../ui/app/src', '/ui/app/src'),
|
||||
sync('../ui/app/index.html', '/ui/app/index.html'),
|
||||
sync('../ui/app/vite.config.ts', '/ui/app/vite.config.ts'),
|
||||
sync('../ui/framework/src', '/ui/framework/src'),
|
||||
],
|
||||
)
|
||||
|
||||
# Docs — nginx serving pre-rendered HTML + Graphviz SVGs
|
||||
docker_build(
|
||||
'nvi-docs',
|
||||
context='..',
|
||||
dockerfile='Dockerfile.docs',
|
||||
live_update=[
|
||||
sync('../docs', '/usr/share/nginx/html'),
|
||||
],
|
||||
)
|
||||
|
||||
# --- Resources ---
|
||||
|
||||
k8s_resource('postgres')
|
||||
k8s_resource('api', resource_deps=['postgres'])
|
||||
k8s_resource('ui', resource_deps=['api'])
|
||||
k8s_resource('docs')
|
||||
k8s_resource('gateway', resource_deps=['ui', 'api', 'docs'])
|
||||
|
||||
# Hot-reload gateway Caddy on Caddyfile edit. configMapGenerator uses
|
||||
# disableNameSuffixHash so the Deployment template doesn't change → kustomize
|
||||
# won't roll the pod on its own. This local_resource closes the loop.
|
||||
local_resource(
|
||||
'gateway-reload',
|
||||
cmd='kubectl --context kind-nvi -n nvi rollout restart deployment/gateway',
|
||||
deps=['k8s/base/Caddyfile'],
|
||||
resource_deps=['gateway'],
|
||||
auto_init=False,
|
||||
)
|
||||
|
||||
# Group infra objects
|
||||
k8s_resource(
|
||||
objects=['nvi:namespace', 'nvi-config:configmap', 'gateway-config:configmap'],
|
||||
new_name='infra',
|
||||
)
|
||||
56
ctrl/docker-compose.yml
Normal file
56
ctrl/docker-compose.yml
Normal file
@@ -0,0 +1,56 @@
|
||||
# Fallback local dev (no kind). Prefer `tilt up` for the real flow.
|
||||
# Usage from repo root: docker compose -f ctrl/docker-compose.yml up -d
|
||||
|
||||
x-common-env: &common-env
|
||||
DATABASE_URL: postgresql://nvi:nvi@postgres:5432/nvi
|
||||
LANGFUSE_HOST: ${LANGFUSE_HOST:-http://host.docker.internal:3000}
|
||||
LANGFUSE_PUBLIC_KEY: ${LANGFUSE_PUBLIC_KEY:-}
|
||||
LANGFUSE_SECRET_KEY: ${LANGFUSE_SECRET_KEY:-}
|
||||
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
|
||||
ANTHROPIC_MODEL: ${ANTHROPIC_MODEL:-claude-sonnet-4-6}
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
environment:
|
||||
POSTGRES_USER: nvi
|
||||
POSTGRES_PASSWORD: nvi
|
||||
POSTGRES_DB: nvi
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U nvi -d nvi"]
|
||||
interval: 5s
|
||||
retries: 10
|
||||
|
||||
api:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: ctrl/Dockerfile.api
|
||||
environment: *common-env
|
||||
ports:
|
||||
- "8000:8000"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
||||
ui:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: ctrl/Dockerfile.ui
|
||||
ports:
|
||||
- "8060:80"
|
||||
depends_on:
|
||||
- api
|
||||
|
||||
docs:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: ctrl/Dockerfile.docs
|
||||
ports:
|
||||
- "8061:80"
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
22
ctrl/k8s/base/Caddyfile
Normal file
22
ctrl/k8s/base/Caddyfile
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
auto_https off
|
||||
admin off
|
||||
}
|
||||
|
||||
# Routes by Host header from the system Caddy (which proxies *.nvi.local.ar
|
||||
# from the host to this gateway via the kind NodePort).
|
||||
|
||||
nvi.local.ar:80 {
|
||||
reverse_proxy ui:80
|
||||
}
|
||||
|
||||
api.nvi.local.ar:80 {
|
||||
reverse_proxy api:8000
|
||||
}
|
||||
|
||||
docs.nvi.local.ar:80 {
|
||||
reverse_proxy docs:80
|
||||
}
|
||||
|
||||
# AWS public hostname later — VPS Caddy will reverse-proxy nivii.mcrn.ar
|
||||
# over WireGuard to this gateway. Add matching blocks when that's wired up.
|
||||
47
ctrl/k8s/base/api.yaml
Normal file
47
ctrl/k8s/base/api.yaml
Normal file
@@ -0,0 +1,47 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: api
|
||||
namespace: nvi
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: api
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: api
|
||||
spec:
|
||||
containers:
|
||||
- name: api
|
||||
image: nvi-api
|
||||
ports:
|
||||
- containerPort: 8000
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: nvi-config
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 8000
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
resources:
|
||||
requests:
|
||||
memory: 256Mi
|
||||
cpu: 200m
|
||||
limits:
|
||||
memory: 1Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: api
|
||||
namespace: nvi
|
||||
spec:
|
||||
selector:
|
||||
app: api
|
||||
ports:
|
||||
- port: 8000
|
||||
targetPort: 8000
|
||||
19
ctrl/k8s/base/configmap.yaml
Normal file
19
ctrl/k8s/base/configmap.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: nvi-config
|
||||
namespace: nvi
|
||||
data:
|
||||
# Warehouse (in-cluster postgres)
|
||||
DATABASE_URL: "postgresql://nvi:nvi@postgres:5432/nvi"
|
||||
|
||||
# Langfuse (lng cluster, reached over WireGuard from the host).
|
||||
# Override LANGFUSE_HOST to your lng WireGuard endpoint; keys come from
|
||||
# the "nvi" project created inside the lng Langfuse UI.
|
||||
LANGFUSE_HOST: "http://lng.local.ar:3000"
|
||||
LANGFUSE_PUBLIC_KEY: ""
|
||||
LANGFUSE_SECRET_KEY: ""
|
||||
|
||||
# LLM
|
||||
ANTHROPIC_API_KEY: ""
|
||||
ANTHROPIC_MODEL: "claude-sonnet-4-6"
|
||||
44
ctrl/k8s/base/docs.yaml
Normal file
44
ctrl/k8s/base/docs.yaml
Normal file
@@ -0,0 +1,44 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: docs
|
||||
namespace: nvi
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: docs
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: docs
|
||||
spec:
|
||||
containers:
|
||||
- name: docs
|
||||
image: nvi-docs
|
||||
ports:
|
||||
- containerPort: 80
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: 2
|
||||
periodSeconds: 10
|
||||
resources:
|
||||
requests:
|
||||
memory: 32Mi
|
||||
cpu: 50m
|
||||
limits:
|
||||
memory: 128Mi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: docs
|
||||
namespace: nvi
|
||||
spec:
|
||||
selector:
|
||||
app: docs
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
53
ctrl/k8s/base/gateway.yaml
Normal file
53
ctrl/k8s/base/gateway.yaml
Normal file
@@ -0,0 +1,53 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: gateway
|
||||
namespace: nvi
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: gateway
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: gateway
|
||||
spec:
|
||||
containers:
|
||||
- name: caddy
|
||||
image: caddy:2-alpine
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: http
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /etc/caddy
|
||||
readOnly: true
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: 80
|
||||
initialDelaySeconds: 2
|
||||
periodSeconds: 10
|
||||
resources:
|
||||
requests:
|
||||
memory: 32Mi
|
||||
cpu: 50m
|
||||
limits:
|
||||
memory: 128Mi
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: gateway-config
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: gateway
|
||||
namespace: nvi
|
||||
spec:
|
||||
selector:
|
||||
app: gateway
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 80
|
||||
23
ctrl/k8s/base/kustomization.yaml
Normal file
23
ctrl/k8s/base/kustomization.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: nvi
|
||||
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- configmap.yaml
|
||||
- postgres.yaml
|
||||
- api.yaml
|
||||
- ui.yaml
|
||||
- docs.yaml
|
||||
- gateway.yaml
|
||||
|
||||
# Hash suffix disabled so the name stays static — lets Tilt group it under the
|
||||
# 'infra' resource. Pod doesn't auto-restart on Caddyfile edit; the Tiltfile's
|
||||
# 'gateway-reload' local_resource closes that loop.
|
||||
configMapGenerator:
|
||||
- name: gateway-config
|
||||
files:
|
||||
- Caddyfile
|
||||
options:
|
||||
disableNameSuffixHash: true
|
||||
4
ctrl/k8s/base/namespace.yaml
Normal file
4
ctrl/k8s/base/namespace.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: nvi
|
||||
60
ctrl/k8s/base/postgres.yaml
Normal file
60
ctrl/k8s/base/postgres.yaml
Normal file
@@ -0,0 +1,60 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: postgres
|
||||
namespace: nvi
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: postgres
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: postgres
|
||||
spec:
|
||||
containers:
|
||||
- name: postgres
|
||||
image: postgres:16-alpine
|
||||
ports:
|
||||
- containerPort: 5432
|
||||
env:
|
||||
- name: POSTGRES_USER
|
||||
value: "nvi"
|
||||
- name: POSTGRES_PASSWORD
|
||||
value: "nvi"
|
||||
- name: POSTGRES_DB
|
||||
value: "nvi"
|
||||
- name: PGDATA
|
||||
value: "/var/lib/postgresql/data/pgdata"
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /var/lib/postgresql/data
|
||||
readinessProbe:
|
||||
exec:
|
||||
command: ["pg_isready", "-U", "nvi", "-d", "nvi"]
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
requests:
|
||||
memory: 256Mi
|
||||
cpu: 100m
|
||||
limits:
|
||||
memory: 1Gi
|
||||
volumes:
|
||||
- name: data
|
||||
hostPath:
|
||||
path: /data/postgres
|
||||
type: DirectoryOrCreate
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: postgres
|
||||
namespace: nvi
|
||||
spec:
|
||||
selector:
|
||||
app: postgres
|
||||
ports:
|
||||
- port: 5432
|
||||
targetPort: 5432
|
||||
44
ctrl/k8s/base/ui.yaml
Normal file
44
ctrl/k8s/base/ui.yaml
Normal file
@@ -0,0 +1,44 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ui
|
||||
namespace: nvi
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ui
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: ui
|
||||
spec:
|
||||
containers:
|
||||
- name: ui
|
||||
image: nvi-ui
|
||||
ports:
|
||||
- containerPort: 80
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: 2
|
||||
periodSeconds: 10
|
||||
resources:
|
||||
requests:
|
||||
memory: 64Mi
|
||||
cpu: 50m
|
||||
limits:
|
||||
memory: 128Mi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: ui
|
||||
namespace: nvi
|
||||
spec:
|
||||
selector:
|
||||
app: ui
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
18
ctrl/k8s/kind-config.yaml
Normal file
18
ctrl/k8s/kind-config.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
kind: Cluster
|
||||
apiVersion: kind.x-k8s.io/v1alpha4
|
||||
name: nvi
|
||||
nodes:
|
||||
- role: control-plane
|
||||
extraPortMappings:
|
||||
# In-cluster Caddy gateway. Routes by Host header:
|
||||
# nvi.local.ar → ui
|
||||
# api.nvi.local.ar → api
|
||||
# docs.nvi.local.ar → docs
|
||||
- containerPort: 30060
|
||||
hostPort: 8060
|
||||
listenAddress: "0.0.0.0"
|
||||
protocol: TCP
|
||||
extraMounts:
|
||||
# Postgres data persists across cluster recreations.
|
||||
- hostPath: /home/mariano/wdir/nvi/.data
|
||||
containerPath: /data
|
||||
19
ctrl/k8s/overlays/dev/kustomization.yaml
Normal file
19
ctrl/k8s/overlays/dev/kustomization.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- ../../base
|
||||
|
||||
patches:
|
||||
# In-cluster Caddy gateway owns the single NodePort. Routes by Host header
|
||||
# to ui / api / docs via service DNS inside the cluster.
|
||||
- target:
|
||||
kind: Service
|
||||
name: gateway
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/type
|
||||
value: NodePort
|
||||
- op: add
|
||||
path: /spec/ports/0/nodePort
|
||||
value: 30060
|
||||
14
ctrl/kind-config.sh
Executable file
14
ctrl/kind-config.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
mkdir -p ../.data/postgres
|
||||
|
||||
if kind get clusters 2>/dev/null | grep -qx nvi; then
|
||||
echo "kind cluster 'nvi' already exists"
|
||||
else
|
||||
echo "creating kind cluster 'nvi'..."
|
||||
kind create cluster --config k8s/kind-config.yaml
|
||||
fi
|
||||
|
||||
kubectl config use-context kind-nvi
|
||||
9
ctrl/nginx.conf
Normal file
9
ctrl/nginx.conf
Normal file
@@ -0,0 +1,9 @@
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user