chore(infra): production manifests + CI for scheduling apps
ci / typecheck (map[dir:apps/booking name:booking]) (push) Has been cancelled
ci / typecheck (map[dir:apps/portal name:portal]) (push) Has been cancelled
ci / typecheck (map[dir:apps/website name:website]) (push) Has been cancelled
ci / typecheck (map[dir:services/platform-api name:platform-api]) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / typecheck (map[dir:apps/booking name:booking]) (push) Has been cancelled
ci / typecheck (map[dir:apps/portal name:portal]) (push) Has been cancelled
ci / typecheck (map[dir:apps/website name:website]) (push) Has been cancelled
ci / typecheck (map[dir:services/platform-api name:platform-api]) (push) Has been cancelled
ci / test (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
# CI for the dezky monorepo (Gitea Actions). Installs deps and typechecks each
|
||||
# app/service independently — the repo is NOT a single pnpm workspace yet, so
|
||||
# every app has its own lockfile and is built from its own directory.
|
||||
name: ci
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- { name: platform-api, dir: services/platform-api }
|
||||
- { name: portal, dir: apps/portal }
|
||||
- { name: booking, dir: apps/booking }
|
||||
- { name: website, dir: apps/website }
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ matrix.target.dir }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
cache-dependency-path: ${{ matrix.target.dir }}/pnpm-lock.yaml
|
||||
|
||||
- name: Install
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm typecheck
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: services/platform-api
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
cache-dependency-path: services/platform-api/pnpm-lock.yaml
|
||||
- name: Install
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Test
|
||||
run: pnpm test
|
||||
@@ -0,0 +1,58 @@
|
||||
# CI for the dezky monorepo (Gitea Actions). Installs deps and typechecks each
|
||||
# app/service independently — the repo is NOT a single pnpm workspace yet, so
|
||||
# every app has its own lockfile and is built from its own directory.
|
||||
name: ci
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- { name: platform-api, dir: services/platform-api }
|
||||
- { name: portal, dir: apps/portal }
|
||||
- { name: booking, dir: apps/booking }
|
||||
- { name: website, dir: apps/website }
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ matrix.target.dir }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
cache-dependency-path: ${{ matrix.target.dir }}/pnpm-lock.yaml
|
||||
|
||||
- name: Install
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm typecheck
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: services/platform-api
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
cache-dependency-path: services/platform-api/pnpm-lock.yaml
|
||||
- name: Install
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Test
|
||||
run: pnpm test
|
||||
@@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
.nuxt
|
||||
.output
|
||||
.git
|
||||
dist
|
||||
*.log
|
||||
@@ -0,0 +1,24 @@
|
||||
# Production image for the dezky customer portal (Nuxt 4 SSR).
|
||||
# Build context = this directory (apps/portal).
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM node:22-alpine AS build
|
||||
WORKDIR /app
|
||||
RUN corepack enable
|
||||
# Install deps first for layer caching (pnpm version comes from packageManager).
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile
|
||||
COPY . .
|
||||
RUN pnpm build
|
||||
|
||||
FROM node:22-alpine AS runtime
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
ENV HOST=0.0.0.0
|
||||
ENV PORT=3000
|
||||
ENV NUXT_PUBLIC_PORTAL_URL=https://app.dezky.eu
|
||||
# OIDC, NUXT_PUBLIC_AUTH_URL and PLATFORM_API_INTERNAL_URL are injected at
|
||||
# deploy time (see infrastructure/production/fleet/README.md).
|
||||
COPY --from=build /app/.output ./.output
|
||||
EXPOSE 3000
|
||||
CMD ["node", ".output/server/index.mjs"]
|
||||
@@ -0,0 +1,118 @@
|
||||
# dezky production — fleet (k3s app tier)
|
||||
|
||||
k3s manifests for the dezky **application tier** that runs in-cluster on the
|
||||
Hetzner AX41 node (see `../host/README.md` for the host layer). This layer
|
||||
deploys the three first-party apps:
|
||||
|
||||
| App | Image | Public host | Internal Service |
|
||||
|-----|-------|-------------|------------------|
|
||||
| platform-api | `git.lastcloud.io/ronnibaslund/dezky/platform-api` | `api.dezky.eu` | `platform-api.dezky-apps:3001` |
|
||||
| portal | `git.lastcloud.io/ronnibaslund/dezky/portal` | `app.dezky.eu` | `portal.dezky-apps:3000` |
|
||||
| booking | `git.lastcloud.io/ronnibaslund/dezky/booking` | `booking.dezky.eu` | `booking.dezky-apps:3000` |
|
||||
|
||||
All three live in the `dezky-apps` namespace. The data tier (Postgres/Mongo/
|
||||
Redis), Authentik and OCIS are added by other parts of the fleet layer and live
|
||||
in their own namespaces; these manifests reference them by cluster DNS only.
|
||||
|
||||
## Files
|
||||
|
||||
```
|
||||
apps/
|
||||
├── kustomization.yaml # bundles the non-secret resources
|
||||
├── namespace.yaml # dezky-apps namespace
|
||||
├── platform-api.yaml # Deployment + Service + Ingress (api.dezky.eu)
|
||||
├── platform-api-config.yaml # non-secret ConfigMap (Stalwart URL, toggles)
|
||||
├── portal.yaml # Deployment + Service + Ingress (app.dezky.eu)
|
||||
├── booking.yaml # Deployment + Service + Ingress (booking.dezky.eu)
|
||||
└── secrets.example.yaml # SECRET TEMPLATE — never commit real values
|
||||
```
|
||||
|
||||
## Prerequisites (other fleet layers)
|
||||
|
||||
These manifests assume the cluster already has:
|
||||
|
||||
- **Traefik** ingress controller (ships with k3s) — `ingressClassName: traefik`.
|
||||
- **cert-manager** with a `ClusterIssuer` named **`letsencrypt-prod`** (HTTP-01).
|
||||
The Ingresses request TLS certs via the `cert-manager.io/cluster-issuer`
|
||||
annotation; cert-manager fills the named `*-tls` Secrets.
|
||||
- **MongoDB** reachable at `mongo.dezky-data.svc.cluster.local:27017`.
|
||||
- **Authentik** reachable publicly at `https://auth.dezky.eu` with OIDC clients
|
||||
provisioned for the portal.
|
||||
- **Stalwart** on the host, reachable from the pod CIDR at its node-internal IP
|
||||
on `:8080` (JMAP management). Update the placeholder IP in
|
||||
`platform-api-config.yaml`.
|
||||
|
||||
## DNS
|
||||
|
||||
Point these A/AAAA records at the AX41 public IP:
|
||||
|
||||
```
|
||||
api.dezky.eu → <AX41 IP>
|
||||
app.dezky.eu → <AX41 IP>
|
||||
booking.dezky.eu → <AX41 IP>
|
||||
```
|
||||
|
||||
## Deploy
|
||||
|
||||
```bash
|
||||
# 1) Apply real Secrets out-of-band (NOT from git). Copy the template,
|
||||
# fill in values, apply — or render SealedSecrets from it.
|
||||
cp apps/secrets.example.yaml /tmp/dezky-secrets.yaml
|
||||
$EDITOR /tmp/dezky-secrets.yaml # fill every REPLACE / PASSWORD
|
||||
kubectl apply -f /tmp/dezky-secrets.yaml
|
||||
rm /tmp/dezky-secrets.yaml
|
||||
|
||||
# 2) Apply the app tier.
|
||||
kubectl apply -k apps/
|
||||
|
||||
# 3) Watch rollout + cert issuance.
|
||||
kubectl -n dezky-apps rollout status deploy/platform-api
|
||||
kubectl -n dezky-apps get ingress,certificate
|
||||
```
|
||||
|
||||
Images are pushed by CI (`.gitea/workflows/ci.yml`) to the Gitea registry. The
|
||||
manifests reference `:latest` for convenience; for real releases, set the image
|
||||
tag to the commit SHA and bump it per deploy (or wire Fleet/ArgoCD to do it).
|
||||
|
||||
## Required env / secrets
|
||||
|
||||
Non-secret config lives in `platform-api-config.yaml` (ConfigMap) and inline
|
||||
`env:` in each Deployment. Secrets are defined in `secrets.example.yaml` and
|
||||
**must be supplied at deploy time**:
|
||||
|
||||
### platform-api (`platform-api-secrets`)
|
||||
|
||||
| Key | Purpose | How to get it |
|
||||
|-----|---------|---------------|
|
||||
| `MONGODB_URI` | Portal/app database connection | From the in-cluster Mongo credentials |
|
||||
| `SCHEDULING_CREDENTIAL_KEY` | AES key encrypting stored scheduling creds | `openssl rand -hex 32` |
|
||||
| `STALWART_ADMIN_PASSWORD` | JMAP management auth | **Same value** as the host `config.env` |
|
||||
| `STALWART_WEBHOOK_SECRET` | Audit webhook HMAC | **Same value** as the host `config.env` |
|
||||
|
||||
ConfigMap (`platform-api-config`): `STALWART_API_URL`, `STALWART_ADMIN_USER`,
|
||||
`STALWART_PROVISIONING_ENABLED`. `PORT` and `DEZKY_ENV` are set inline.
|
||||
|
||||
### portal (`portal-secrets`)
|
||||
|
||||
| Key | Purpose |
|
||||
|-----|---------|
|
||||
| `NUXT_OIDC_CLIENT_ID` / `NUXT_OIDC_CLIENT_SECRET` | Authentik OIDC client |
|
||||
| `NUXT_OIDC_REDIRECT_URI` | `https://app.dezky.eu/auth/callback` |
|
||||
| `NUXT_OIDC_SESSION_SECRET` | Session encryption (`openssl rand -hex 32`) |
|
||||
| `NUXT_PUBLIC_AUTH_URL` | Public Authentik URL (login + full sign-out) |
|
||||
|
||||
`PLATFORM_API_INTERNAL_URL` / `NUXT_API_BASE` / `NUXT_PUBLIC_PORTAL_URL` are set
|
||||
inline in `portal.yaml`.
|
||||
|
||||
### booking (`booking-secrets`)
|
||||
|
||||
| Key | Purpose |
|
||||
|-----|---------|
|
||||
| `NUXT_PUBLIC_TURNSTILE_SITE_KEY` | Cloudflare Turnstile site key (public, env-injected) |
|
||||
|
||||
`PLATFORM_API_INTERNAL_URL` / `NUXT_PUBLIC_SITE_URL` are set inline in
|
||||
`booking.yaml`.
|
||||
|
||||
> **Never commit real secret values.** `secrets.example.yaml` is a template and
|
||||
> is deliberately excluded from `kustomization.yaml`. Manage real secrets via
|
||||
> sealed-secrets / SOPS / the Rancher secret store.
|
||||
@@ -0,0 +1,98 @@
|
||||
# booking — Nuxt 4 SSR public booking app on booking.dezky.eu. Fully public
|
||||
# (no OIDC); only calls platform-api's /api/v1/public/* through its nitro proxy.
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: booking
|
||||
namespace: dezky-apps
|
||||
labels:
|
||||
app.kubernetes.io/name: booking
|
||||
app.kubernetes.io/part-of: dezky
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: booking
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: booking
|
||||
spec:
|
||||
containers:
|
||||
- name: booking
|
||||
image: git.lastcloud.io/ronnibaslund/dezky/booking:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 3000
|
||||
env:
|
||||
- name: HOST
|
||||
value: "0.0.0.0"
|
||||
- name: PORT
|
||||
value: "3000"
|
||||
- name: NUXT_PUBLIC_SITE_URL
|
||||
value: https://booking.dezky.eu
|
||||
- name: PLATFORM_API_INTERNAL_URL
|
||||
value: http://platform-api.dezky-apps.svc.cluster.local:3001
|
||||
# NUXT_PUBLIC_TURNSTILE_SITE_KEY is public but env-injected so the key
|
||||
# can rotate without a rebuild; lives in the Secret. See README.md.
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: booking-secrets
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 192Mi
|
||||
limits:
|
||||
memory: 512Mi
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: http
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 15
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: http
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 30
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: booking
|
||||
namespace: dezky-apps
|
||||
labels:
|
||||
app.kubernetes.io/name: booking
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/name: booking
|
||||
ports:
|
||||
- name: http
|
||||
port: 3000
|
||||
targetPort: http
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: booking
|
||||
namespace: dezky-apps
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- booking.dezky.eu
|
||||
secretName: booking-dezky-eu-tls
|
||||
rules:
|
||||
- host: booking.dezky.eu
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: booking
|
||||
port:
|
||||
number: 3000
|
||||
@@ -0,0 +1,12 @@
|
||||
# Kustomization for the dezky application tier. Real Secrets are applied
|
||||
# out-of-band (sealed-secrets / SOPS), so secrets.example.yaml is intentionally
|
||||
# NOT listed here — it is a template only.
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: dezky-apps
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- platform-api-config.yaml
|
||||
- platform-api.yaml
|
||||
- portal.yaml
|
||||
- booking.yaml
|
||||
@@ -0,0 +1,9 @@
|
||||
# Namespace for the dezky application tier (portal, booking, platform-api).
|
||||
# The data tier (Postgres/Mongo/Redis), Authentik and OCIS live in their own
|
||||
# namespaces added elsewhere in the fleet layer.
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: dezky-apps
|
||||
labels:
|
||||
app.kubernetes.io/part-of: dezky
|
||||
@@ -0,0 +1,17 @@
|
||||
# Non-secret runtime config for platform-api. Cluster-internal service
|
||||
# addresses and integration toggles. Secrets (Mongo URI, credential key,
|
||||
# Stalwart password, webhook secret) live in the platform-api-secrets Secret.
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: platform-api-config
|
||||
namespace: dezky-apps
|
||||
labels:
|
||||
app.kubernetes.io/name: platform-api
|
||||
data:
|
||||
# Stalwart runs on the HOST (not k3s). Reach it on the node-internal IP at the
|
||||
# JMAP management port; the firewall lets the pod CIDR through. Override the
|
||||
# placeholder IP to match the host's actual internal address.
|
||||
STALWART_API_URL: "http://10.0.0.1:8080"
|
||||
STALWART_ADMIN_USER: "admin"
|
||||
STALWART_PROVISIONING_ENABLED: "true"
|
||||
@@ -0,0 +1,102 @@
|
||||
# platform-api — NestJS control plane (tenants, partners, users, scheduling,
|
||||
# provisioning). Internal-only Service on :3001 plus a public Ingress for
|
||||
# api.dezky.eu (consumed by booking's nitro proxy and the Stalwart webhook).
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: platform-api
|
||||
namespace: dezky-apps
|
||||
labels:
|
||||
app.kubernetes.io/name: platform-api
|
||||
app.kubernetes.io/part-of: dezky
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: platform-api
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: platform-api
|
||||
spec:
|
||||
containers:
|
||||
- name: platform-api
|
||||
# Pinned by CI to the commit SHA on release; :latest is dev-only.
|
||||
image: git.lastcloud.io/ronnibaslund/dezky/platform-api:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 3001
|
||||
env:
|
||||
- name: PORT
|
||||
value: "3001"
|
||||
- name: DEZKY_ENV
|
||||
value: production
|
||||
# Non-secret config (Stalwart URL, feature toggles, etc.) comes from a
|
||||
# ConfigMap; secrets (Mongo URI, credential key, Stalwart password,
|
||||
# webhook secret) come from the Secret. See README.md.
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: platform-api-config
|
||||
- secretRef:
|
||||
name: platform-api-secrets
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 192Mi
|
||||
limits:
|
||||
memory: 512Mi
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: http
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 15
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: http
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 30
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: platform-api
|
||||
namespace: dezky-apps
|
||||
labels:
|
||||
app.kubernetes.io/name: platform-api
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/name: platform-api
|
||||
ports:
|
||||
- name: http
|
||||
port: 3001
|
||||
targetPort: http
|
||||
---
|
||||
# Public ingress for api.dezky.eu. TLS via cert-manager (HTTP-01) + Traefik.
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: platform-api
|
||||
namespace: dezky-apps
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- api.dezky.eu
|
||||
secretName: api-dezky-eu-tls
|
||||
rules:
|
||||
- host: api.dezky.eu
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: platform-api
|
||||
port:
|
||||
number: 3001
|
||||
@@ -0,0 +1,101 @@
|
||||
# portal — Nuxt 4 SSR customer portal on app.dezky.eu. Talks to platform-api
|
||||
# over the cluster network via its nitro proxy (PLATFORM_API_INTERNAL_URL) and
|
||||
# authenticates users through Authentik (OIDC).
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: portal
|
||||
namespace: dezky-apps
|
||||
labels:
|
||||
app.kubernetes.io/name: portal
|
||||
app.kubernetes.io/part-of: dezky
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: portal
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: portal
|
||||
spec:
|
||||
containers:
|
||||
- name: portal
|
||||
image: git.lastcloud.io/ronnibaslund/dezky/portal:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 3000
|
||||
env:
|
||||
- name: HOST
|
||||
value: "0.0.0.0"
|
||||
- name: PORT
|
||||
value: "3000"
|
||||
- name: NUXT_PUBLIC_PORTAL_URL
|
||||
value: https://app.dezky.eu
|
||||
# Cluster-internal address of platform-api for the nitro proxy.
|
||||
- name: PLATFORM_API_INTERNAL_URL
|
||||
value: http://platform-api.dezky-apps.svc.cluster.local:3001
|
||||
- name: NUXT_API_BASE
|
||||
value: http://platform-api.dezky-apps.svc.cluster.local:3001
|
||||
# OIDC client id/secret + Authentik public URL come from the Secret.
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: portal-secrets
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 192Mi
|
||||
limits:
|
||||
memory: 512Mi
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: http
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 15
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: http
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 30
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: portal
|
||||
namespace: dezky-apps
|
||||
labels:
|
||||
app.kubernetes.io/name: portal
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/name: portal
|
||||
ports:
|
||||
- name: http
|
||||
port: 3000
|
||||
targetPort: http
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: portal
|
||||
namespace: dezky-apps
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- app.dezky.eu
|
||||
secretName: app-dezky-eu-tls
|
||||
rules:
|
||||
- host: app.dezky.eu
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: portal
|
||||
port:
|
||||
number: 3000
|
||||
@@ -0,0 +1,52 @@
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Secret TEMPLATE — DO NOT COMMIT REAL VALUES.
|
||||
#
|
||||
# These are placeholders. In production, manage the real Secrets out-of-band
|
||||
# (sealed-secrets / SOPS / Rancher secret store), NOT in git. Copy this file,
|
||||
# fill in real values, and apply it separately — or render SealedSecrets from
|
||||
# it. The Deployments reference these Secrets by name via envFrom.
|
||||
#
|
||||
# Generate strong values with: openssl rand -hex 32
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: platform-api-secrets
|
||||
namespace: dezky-apps
|
||||
type: Opaque
|
||||
stringData:
|
||||
# Mongo connection string for the in-cluster MongoDB (data tier namespace).
|
||||
MONGODB_URI: "mongodb://USER:PASSWORD@mongo.dezky-data.svc.cluster.local:27017/dezky?authSource=admin"
|
||||
# AES key used to encrypt stored scheduling credentials (e.g. CalDAV creds).
|
||||
SCHEDULING_CREDENTIAL_KEY: "REPLACE_WITH_openssl_rand_hex_32"
|
||||
# MUST equal the host's STALWART_ADMIN_PASSWORD (config.env on the AX41).
|
||||
STALWART_ADMIN_PASSWORD: "REPLACE_WITH_SAME_AS_HOST"
|
||||
# MUST equal the host's STALWART_WEBHOOK_SECRET (audit webhook HMAC).
|
||||
STALWART_WEBHOOK_SECRET: "REPLACE_WITH_SAME_AS_HOST"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: portal-secrets
|
||||
namespace: dezky-apps
|
||||
type: Opaque
|
||||
stringData:
|
||||
# Authentik OIDC client provisioned for the portal.
|
||||
NUXT_OIDC_CLIENT_ID: "REPLACE"
|
||||
NUXT_OIDC_CLIENT_SECRET: "REPLACE"
|
||||
NUXT_OIDC_REDIRECT_URI: "https://app.dezky.eu/auth/callback"
|
||||
# Public base URL of Authentik (used for login redirects + full sign-out).
|
||||
NUXT_PUBLIC_AUTH_URL: "https://auth.dezky.eu"
|
||||
# nuxt-oidc-auth session encryption secret (openssl rand -hex 32).
|
||||
NUXT_OIDC_SESSION_SECRET: "REPLACE_WITH_openssl_rand_hex_32"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: booking-secrets
|
||||
namespace: dezky-apps
|
||||
type: Opaque
|
||||
stringData:
|
||||
# Cloudflare Turnstile site key for the public booking form (public value,
|
||||
# env-injected so it can rotate without a rebuild).
|
||||
NUXT_PUBLIC_TURNSTILE_SITE_KEY: "REPLACE"
|
||||
@@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
dist
|
||||
.git
|
||||
*.log
|
||||
@@ -0,0 +1,30 @@
|
||||
# Production image for the dezky platform-api (NestJS, ESM + NodeNext).
|
||||
# Build context = this directory (services/platform-api).
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM node:22-alpine AS build
|
||||
WORKDIR /app
|
||||
RUN corepack enable
|
||||
# Install deps first for layer caching (pnpm version comes from packageManager).
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile
|
||||
COPY . .
|
||||
RUN pnpm build
|
||||
|
||||
FROM node:22-alpine AS deps
|
||||
WORKDIR /app
|
||||
RUN corepack enable
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
# Production-only deps for the runtime layer (no dev/build tooling).
|
||||
RUN pnpm install --frozen-lockfile --prod
|
||||
|
||||
FROM node:22-alpine AS runtime
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
ENV PORT=3001
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY --from=build /app/dist ./dist
|
||||
COPY package.json ./
|
||||
EXPOSE 3001
|
||||
# main.ts compiles to dist/main.js; NodeNext ESM output is run directly by node.
|
||||
CMD ["node", "dist/main.js"]
|
||||
Reference in New Issue
Block a user