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
|
||||
|
||||
|
||||
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
|
||||
node_modules/
|
||||
|
||||
# Build output
|
||||
# Build
|
||||
.next/
|
||||
out/
|
||||
dist/
|
||||
|
||||
# Development
|
||||
.env*.local
|
||||
.env
|
||||
# Environment
|
||||
.env*
|
||||
!.env.example
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
|
||||
# Misc
|
||||
*.log
|
||||
# OS
|
||||
.DS_Store
|
||||
|
||||
# Generated
|
||||
public/openapi.json
|
||||
# Logs
|
||||
*.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";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
output: "standalone",
|
||||
};
|
||||
|
||||
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 { NoteHeader } from "@/components/notes/NoteHeader";
|
||||
import { PromptsSection } from "@/components/notes/PromptsSection";
|
||||
import { SkillsSection } from "@/components/notes/SkillsSection";
|
||||
import { FilesSection } from "@/components/notes/FilesSection";
|
||||
import { NoteFooter } from "@/components/notes/NoteFooter";
|
||||
|
||||
@ -34,6 +35,8 @@ export default async function NotePage({ params }: NotePageProps) {
|
||||
|
||||
<PromptsSection prompts={note.prompts} />
|
||||
|
||||
<SkillsSection skills={note.skillsUsed || []} />
|
||||
|
||||
<FilesSection files={note.files} />
|
||||
|
||||
<div className="prose prose-neutral dark:prose-invert max-w-none">
|
||||
|
||||
@ -30,6 +30,13 @@ export interface FileRef {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface SkillUsed {
|
||||
name: string;
|
||||
command: string;
|
||||
description: string;
|
||||
usage?: string;
|
||||
}
|
||||
|
||||
export interface NoteNavLink {
|
||||
slug: string;
|
||||
id: string;
|
||||
@ -43,6 +50,7 @@ export interface NoteMeta {
|
||||
title: string;
|
||||
preview: string;
|
||||
prompts: Prompt[];
|
||||
skillsUsed?: SkillUsed[];
|
||||
filesCreated: FileRef[];
|
||||
navigation: {
|
||||
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