Initial orchard9 deployment
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- Add Dockerfile with multi-stage standalone build - Add Woodpecker CI pipeline (.woodpecker.yml) - Add Kubernetes manifests for deployment, service, ingress - Add ops.md with deployment documentation - Configure Next.js for standalone output - Move deployment files to root level Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
9a9e58c935
commit
a65c3f7243
@ -1,6 +1,6 @@
|
|||||||
# Creating Blog Notes
|
# Creating Research Notes
|
||||||
|
|
||||||
This skill documents how to create new research notes for the Maxwell blog.
|
This skill documents how to create new research notes for the journal.
|
||||||
|
|
||||||
## Directory Structure
|
## Directory Structure
|
||||||
|
|
||||||
|
|||||||
26
.dockerignore
Normal file
26
.dockerignore
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Git
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# Dependencies (will be installed fresh in container)
|
||||||
|
blog/node_modules/
|
||||||
|
|
||||||
|
# Build output
|
||||||
|
blog/.next/
|
||||||
|
blog/out/
|
||||||
|
|
||||||
|
# Dev files
|
||||||
|
blog/.env*
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Docs
|
||||||
|
*.md
|
||||||
|
!blog/content/**/*.md
|
||||||
|
|
||||||
|
# Claude
|
||||||
|
.claude/
|
||||||
16
blog/.dockerignore → .gitignore
vendored
16
blog/.dockerignore → .gitignore
vendored
@ -1,22 +1,22 @@
|
|||||||
# Dependencies
|
# Dependencies
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
# Build output
|
# Build
|
||||||
.next/
|
.next/
|
||||||
out/
|
out/
|
||||||
dist/
|
dist/
|
||||||
|
|
||||||
# Development
|
# Environment
|
||||||
.env*.local
|
.env*
|
||||||
.env
|
!.env.example
|
||||||
|
|
||||||
# IDE
|
# IDE
|
||||||
.vscode/
|
.vscode/
|
||||||
|
.idea/
|
||||||
*.swp
|
*.swp
|
||||||
|
|
||||||
# Misc
|
# OS
|
||||||
*.log
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
# Generated
|
# Logs
|
||||||
public/openapi.json
|
*.log
|
||||||
33
.woodpecker.yml
Normal file
33
.woodpecker.yml
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# CI/CD Pipeline for research-notes
|
||||||
|
|
||||||
|
clone:
|
||||||
|
git:
|
||||||
|
image: woodpeckerci/plugin-git
|
||||||
|
settings:
|
||||||
|
depth: 1
|
||||||
|
|
||||||
|
steps:
|
||||||
|
build:
|
||||||
|
image: woodpeckerci/plugin-kaniko
|
||||||
|
settings:
|
||||||
|
registry: registry.threesix.ai
|
||||||
|
repo: research-notes/web
|
||||||
|
tags:
|
||||||
|
- latest
|
||||||
|
- ${CI_COMMIT_SHA:0:8}
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
cache: true
|
||||||
|
skip_tls_verify: true
|
||||||
|
when:
|
||||||
|
branch: main
|
||||||
|
event: push
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
image: bitnami/kubectl:latest
|
||||||
|
commands:
|
||||||
|
- kubectl set image deployment/research-notes web=registry.threesix.ai/research-notes/web:${CI_COMMIT_SHA:0:8} -n projects
|
||||||
|
- kubectl rollout status deployment/research-notes -n projects --timeout=120s
|
||||||
|
when:
|
||||||
|
branch: main
|
||||||
|
event: push
|
||||||
42
Dockerfile
Normal file
42
Dockerfile
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# Build stage
|
||||||
|
FROM node:20-alpine AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install pnpm
|
||||||
|
RUN corepack enable && corepack prepare pnpm@latest --activate
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY blog/package.json blog/pnpm-lock.yaml ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
# Copy source
|
||||||
|
COPY blog/ .
|
||||||
|
|
||||||
|
# Build
|
||||||
|
RUN pnpm build
|
||||||
|
|
||||||
|
# Runtime stage
|
||||||
|
FROM node:20-alpine AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV PORT=3000
|
||||||
|
|
||||||
|
# Don't run as root
|
||||||
|
RUN addgroup --system --gid 1001 nodejs && \
|
||||||
|
adduser --system --uid 1001 nextjs
|
||||||
|
|
||||||
|
# Copy standalone build
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
|
||||||
|
|
||||||
|
# Content is read at build time for SSG, but copy for any runtime needs
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/content ./content
|
||||||
|
|
||||||
|
USER nextjs
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD ["node", "server.js"]
|
||||||
@ -1,55 +0,0 @@
|
|||||||
# StemeDB Community App Docker Build
|
|
||||||
#
|
|
||||||
# Multi-stage build for the Next.js frontend.
|
|
||||||
# Also used for running the seed script.
|
|
||||||
|
|
||||||
# Stage 1: Dependencies
|
|
||||||
FROM node:20-slim AS deps
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy package files
|
|
||||||
COPY package*.json ./
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
RUN npm ci
|
|
||||||
|
|
||||||
# Stage 2: Builder
|
|
||||||
FROM node:20-slim AS builder
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy dependencies from deps stage
|
|
||||||
COPY --from=deps /app/node_modules ./node_modules
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Create empty openapi.json if it doesn't exist (will be fetched at runtime)
|
|
||||||
RUN mkdir -p public && echo '{}' > public/openapi.json
|
|
||||||
|
|
||||||
# Build the Next.js app
|
|
||||||
# Note: Build may fail if API is not available, but we continue anyway
|
|
||||||
RUN npm run build || echo "Build completed with warnings"
|
|
||||||
|
|
||||||
# Stage 3: Runtime
|
|
||||||
FROM node:20-slim AS runner
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
|
||||||
ENV PORT=19197
|
|
||||||
|
|
||||||
# Copy built assets and dependencies
|
|
||||||
COPY --from=builder /app/.next ./.next
|
|
||||||
COPY --from=builder /app/public ./public
|
|
||||||
COPY --from=builder /app/package*.json ./
|
|
||||||
COPY --from=builder /app/node_modules ./node_modules
|
|
||||||
|
|
||||||
# Copy scripts directory for seed script
|
|
||||||
COPY --from=builder /app/scripts ./scripts
|
|
||||||
COPY --from=builder /app/tsconfig.json ./tsconfig.json
|
|
||||||
COPY --from=builder /app/src ./src
|
|
||||||
|
|
||||||
EXPOSE 19197
|
|
||||||
|
|
||||||
# Default command runs the Next.js server
|
|
||||||
CMD ["npm", "run", "start"]
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import type { NextConfig } from "next";
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
/* config options here */
|
output: "standalone",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
4974
blog/pnpm-lock.yaml
generated
Normal file
4974
blog/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -6,6 +6,7 @@ import { PageLayout } from "@/components/layout/PageLayout";
|
|||||||
import { BackNav } from "@/components/layout/BackNav";
|
import { BackNav } from "@/components/layout/BackNav";
|
||||||
import { NoteHeader } from "@/components/notes/NoteHeader";
|
import { NoteHeader } from "@/components/notes/NoteHeader";
|
||||||
import { PromptsSection } from "@/components/notes/PromptsSection";
|
import { PromptsSection } from "@/components/notes/PromptsSection";
|
||||||
|
import { SkillsSection } from "@/components/notes/SkillsSection";
|
||||||
import { FilesSection } from "@/components/notes/FilesSection";
|
import { FilesSection } from "@/components/notes/FilesSection";
|
||||||
import { NoteFooter } from "@/components/notes/NoteFooter";
|
import { NoteFooter } from "@/components/notes/NoteFooter";
|
||||||
|
|
||||||
@ -34,6 +35,8 @@ export default async function NotePage({ params }: NotePageProps) {
|
|||||||
|
|
||||||
<PromptsSection prompts={note.prompts} />
|
<PromptsSection prompts={note.prompts} />
|
||||||
|
|
||||||
|
<SkillsSection skills={note.skillsUsed || []} />
|
||||||
|
|
||||||
<FilesSection files={note.files} />
|
<FilesSection files={note.files} />
|
||||||
|
|
||||||
<div className="prose prose-neutral dark:prose-invert max-w-none">
|
<div className="prose prose-neutral dark:prose-invert max-w-none">
|
||||||
|
|||||||
@ -30,6 +30,13 @@ export interface FileRef {
|
|||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SkillUsed {
|
||||||
|
name: string;
|
||||||
|
command: string;
|
||||||
|
description: string;
|
||||||
|
usage?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface NoteNavLink {
|
export interface NoteNavLink {
|
||||||
slug: string;
|
slug: string;
|
||||||
id: string;
|
id: string;
|
||||||
@ -43,6 +50,7 @@ export interface NoteMeta {
|
|||||||
title: string;
|
title: string;
|
||||||
preview: string;
|
preview: string;
|
||||||
prompts: Prompt[];
|
prompts: Prompt[];
|
||||||
|
skillsUsed?: SkillUsed[];
|
||||||
filesCreated: FileRef[];
|
filesCreated: FileRef[];
|
||||||
navigation: {
|
navigation: {
|
||||||
prev: NoteNavLink | null;
|
prev: NoteNavLink | null;
|
||||||
|
|||||||
76
deploy/k8s/notes.yaml
Normal file
76
deploy/k8s/notes.yaml
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: research-notes
|
||||||
|
namespace: projects
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: research-notes
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: research-notes
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: web
|
||||||
|
image: registry.threesix.ai/research-notes/web:latest
|
||||||
|
ports:
|
||||||
|
- containerPort: 3000
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "64Mi"
|
||||||
|
cpu: "25m"
|
||||||
|
limits:
|
||||||
|
memory: "256Mi"
|
||||||
|
cpu: "250m"
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 3000
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 30
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 3000
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: research-notes
|
||||||
|
namespace: projects
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: research-notes
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 3000
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: research-notes
|
||||||
|
namespace: projects
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- notes.orchard9.ai
|
||||||
|
secretName: research-notes-tls
|
||||||
|
rules:
|
||||||
|
- host: notes.orchard9.ai
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: research-notes
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
144
ops.md
Normal file
144
ops.md
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
# Operations: notes.orchard9.ai
|
||||||
|
|
||||||
|
Research notes journal deployed to orchard9 k3s fleet.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────┐ push ┌─────────────┐ webhook ┌─────────────┐
|
||||||
|
│ Local │ ────────► │ Gitea │ ─────────► │ Woodpecker │
|
||||||
|
│ Dev │ │ threesix.ai │ │ CI │
|
||||||
|
└─────────────┘ └─────────────┘ └──────┬──────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────┐ ingress ┌─────────────┐ deploy ┌─────────────┐
|
||||||
|
│ Browser │ ◄──────── │ k3s │ ◄──────── │ Kaniko │
|
||||||
|
│ notes. │ │ projects │ │ build │
|
||||||
|
│ orchard9.ai │ │ namespace │ └──────┬──────┘
|
||||||
|
└─────────────┘ └─────────────┘ │
|
||||||
|
▼
|
||||||
|
┌─────────────┐
|
||||||
|
│ Zot Registry│
|
||||||
|
│ registry. │
|
||||||
|
│ threesix.ai │
|
||||||
|
└─────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Infrastructure
|
||||||
|
|
||||||
|
| Component | Location |
|
||||||
|
|-----------|----------|
|
||||||
|
| Domain | notes.orchard9.ai |
|
||||||
|
| DNS Provider | GoDaddy (via squiddy-dns) |
|
||||||
|
| Ingress IP | 208.122.204.172 |
|
||||||
|
| TLS | cert-manager / letsencrypt-prod |
|
||||||
|
| Registry | registry.threesix.ai |
|
||||||
|
| Git Origin | git.threesix.ai/jordan/research-notes |
|
||||||
|
| Namespace | projects |
|
||||||
|
|
||||||
|
## Local Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd blog
|
||||||
|
npm install # or pnpm install
|
||||||
|
npm run dev # http://localhost:19197
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
Push to origin triggers automatic deployment:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
Pipeline:
|
||||||
|
1. Woodpecker receives webhook from Gitea
|
||||||
|
2. Kaniko builds container image (amd64)
|
||||||
|
3. Image pushed to `registry.threesix.ai/research-notes/web:${SHA}`
|
||||||
|
4. kubectl rolls out new image to deployment
|
||||||
|
|
||||||
|
## Initial Setup (one-time)
|
||||||
|
|
||||||
|
### 1. Create Gitea Repository
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create repo at git.threesix.ai/jordan/research-notes
|
||||||
|
# Then set origin:
|
||||||
|
git remote add origin https://git.threesix.ai/jordan/research-notes.git
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Configure DNS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
squiddy-dns record create orchard9.ai A notes 208.122.204.172 \
|
||||||
|
--ttl 300 --provider godaddy --profile orchard9
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Apply Kubernetes Manifests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export KUBECONFIG=~/.kube/orchard9-k3sf.yaml
|
||||||
|
kubectl apply -f deploy/k8s/notes.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. First Deploy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .
|
||||||
|
git commit -m "Initial deployment setup"
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verify Deployment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check pod status
|
||||||
|
kubectl get pods -n projects -l app=research-notes
|
||||||
|
|
||||||
|
# Check ingress
|
||||||
|
kubectl get ingress -n projects research-notes
|
||||||
|
|
||||||
|
# Check TLS certificate
|
||||||
|
kubectl get certificate -n projects research-notes-tls
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
kubectl logs -n projects -l app=research-notes --tail=50
|
||||||
|
|
||||||
|
# Port forward for debugging
|
||||||
|
kubectl port-forward -n projects svc/research-notes 8080:80
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Build not triggering?
|
||||||
|
- Verify push went to `origin` (Gitea), not GitHub
|
||||||
|
- Check Woodpecker webhook exists on Gitea repo
|
||||||
|
- Check Woodpecker at ci.threesix.ai
|
||||||
|
|
||||||
|
### Image not deploying?
|
||||||
|
```bash
|
||||||
|
# Check if image exists in registry
|
||||||
|
curl -s https://registry.threesix.ai/v2/research-notes/web/tags/list
|
||||||
|
|
||||||
|
# Check deployment events
|
||||||
|
kubectl describe deployment -n projects research-notes
|
||||||
|
```
|
||||||
|
|
||||||
|
### TLS certificate not ready?
|
||||||
|
```bash
|
||||||
|
# Check certificate status
|
||||||
|
kubectl describe certificate -n projects research-notes-tls
|
||||||
|
|
||||||
|
# Check cert-manager logs
|
||||||
|
kubectl logs -n cert-manager -l app=cert-manager --tail=50
|
||||||
|
```
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `Dockerfile` | Multi-stage Next.js standalone build |
|
||||||
|
| `.woodpecker.yml` | CI/CD pipeline config |
|
||||||
|
| `deploy/k8s/notes.yaml` | Deployment, Service, Ingress |
|
||||||
|
| `blog/next.config.ts` | Next.js config (standalone output) |
|
||||||
Loading…
Reference in New Issue
Block a user