Add Dockerfile, K8s manifests, and deploy.sh for plexnuke k3s cluster

This commit is contained in:
2026-06-27 16:51:01 -05:00
parent 1bd5325baa
commit 3bf7642343
7 changed files with 236 additions and 1 deletions
+31
View File
@@ -0,0 +1,31 @@
FROM node:20-alpine AS base
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package*.json ./
RUN npm ci
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]
+1
View File
@@ -62,6 +62,7 @@ stringData:
POSTGRES_USER: lahuasca POSTGRES_USER: lahuasca
POSTGRES_PASSWORD: "$PASSWORD" POSTGRES_PASSWORD: "$PASSWORD"
POSTGRES_DB: lahuasca POSTGRES_DB: lahuasca
DATABASE_URL: "postgres://lahuasca:${PASSWORD}@postgres:5432/lahuasca"
EOF EOF
echo "Database secret updated in namespace $NAMESPACE" echo "Database secret updated in namespace $NAMESPACE"
+80
View File
@@ -0,0 +1,80 @@
#!/usr/bin/env bash
set -euo pipefail
# Deploy la-huasca-ts to the plexnuke k3s cluster and expose it on lahuasca.com
#
# Usage:
# ./deploy.sh
# REGISTRY_USER=muken REGISTRY_PASS=... ./deploy.sh
#
# Prerequisites on the target machine (plexnuke):
# - kubectl configured for the k3s cluster
# - docker or buildah/push capability (we build locally and push)
# - registry secret exists or REGISTRY_USER/REGISTRY_PASS are exported
NAMESPACE="la-huasca"
IMAGE="gitea.ajavasoft.com/muken/la-huasca-ts"
TAG="${TAG:-$(git rev-parse --short HEAD)}"
HOSTS=("lahuasca.com" "www.lahuasca.com")
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
# Ensure the namespace exists
kubectl create namespace "$NAMESPACE" 2>/dev/null || true
# Build Next.js standalone output and Docker image
echo "==> Building Docker image $IMAGE:$TAG ..."
DOCKER_BUILDKIT=1 docker build -t "$IMAGE:$TAG" -t "$IMAGE:latest" .
# Push to registry
echo "==> Pushing image ..."
docker push "$IMAGE:$TAG"
docker push "$IMAGE:latest"
# Generate JWT secret if not present
if ! kubectl get secret la-huasca-app-secrets -n "$NAMESPACE" >/dev/null 2>&1; then
JWT_SECRET="$(openssl rand -base64 48 | tr -dc 'a-zA-Z0-9' | head -c 64)"
kubectl create secret generic la-huasca-app-secrets \
--namespace "$NAMESPACE" \
--from-literal=JWT_SECRET="$JWT_SECRET"
echo "==> Created la-huasca-app-secrets with JWT_SECRET"
fi
# Create database secret if missing (run deploy-db.sh first to populate it)
if ! kubectl get secret la-huasca-db-secrets -n "$NAMESPACE" >/dev/null 2>&1; then
echo "WARNING: la-huasca-db-secrets not found. Run ./deploy-db.sh first."
exit 1
fi
# Build registry pull secret if credentials are provided
if [ -n "${REGISTRY_USER:-}" ] && [ -n "${REGISTRY_PASS:-}" ]; then
REGISTRY_AUTH="$(printf '%s:%s' "$REGISTRY_USER" "$REGISTRY_PASS" | base64 -w 0)"
export REGISTRY_USER REGISTRY_PASS REGISTRY_AUTH
echo "==> Updating registry pull secret ..."
envsubst \u003c "./k8s/registry-secret.yaml" | kubectl apply -f -
fi
# Update image tag in manifest and apply
export TAG
export NAMESPACE
envsubst \u003c "./k8s/app.yaml" | kubectl apply -f -
# Wait for rollout
echo "==> Waiting for rollout ..."
kubectl rollout status deployment/la-huasca-nextjs -n "$NAMESPACE" --timeout=180s
# Print summary
IP="$(kubectl get service traefik -n kube-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null || echo 'node IP')"
echo ""
echo "Deploy complete."
echo " Image: $IMAGE:$TAG"
echo " Hosts: ${HOSTS[*]}"
echo " LB IP: $IP"
echo ""
echo "To seed the database, run:"
echo " kubectl exec -n $NAMESPACE deploy/la-huasca-nextjs -- curl -s -X POST http://localhost:3000/api/db/init"
echo ""
echo "To bootstrap mmendoza admin user:"
echo " kubectl exec -n $NAMESPACE deploy/la-huasca-nextjs -- curl -s -X POST http://localhost:3000/api/bootstrap-user"
+105
View File
@@ -0,0 +1,105 @@
apiVersion: v1
kind: Namespace
metadata:
name: ${NAMESPACE}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: la-huasca-nextjs
namespace: ${NAMESPACE}
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: la-huasca-nextjs
template:
metadata:
labels:
app: la-huasca-nextjs
spec:
imagePullSecrets:
- name: la-huasca-registry
containers:
- name: nextjs
image: gitea.ajavasoft.com/muken/la-huasca-ts:${TAG}
ports:
- containerPort: 3000
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: la-huasca-db-secrets
key: DATABASE_URL
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: la-huasca-app-secrets
key: JWT_SECRET
readinessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 15
periodSeconds: 10
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: la-huasca-nextjs
namespace: ${NAMESPACE}
spec:
selector:
app: la-huasca-nextjs
ports:
- port: 80
targetPort: 3000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: la-huasca-nextjs
namespace: ${NAMESPACE}
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
spec:
ingressClassName: traefik
rules:
- host: lahuasca.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: la-huasca-nextjs
port:
number: 80
- host: www.lahuasca.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: la-huasca-nextjs
port:
number: 80
+17
View File
@@ -0,0 +1,17 @@
apiVersion: v1
kind: Secret
metadata:
name: la-huasca-registry
namespace: ${NAMESPACE}
type: kubernetes.io/dockerconfigjson
stringData:
.dockerconfigjson: |
{
"auths": {
"gitea.ajavasoft.com": {
"username": "${REGISTRY_USER}",
"password": "${REGISTRY_PASS}",
"auth": "${REGISTRY_AUTH}"
}
}
}
+1
View File
@@ -1,6 +1,7 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
reactStrictMode: true, reactStrictMode: true,
output: 'standalone',
}; };
module.exports = nextConfig; module.exports = nextConfig;
+1 -1
View File
File diff suppressed because one or more lines are too long