feat(deploy): aibook.fun deploy layer (terraform + ansible + docker on DO)

This commit is contained in:
dilonne
2026-09-02 10:57:09 +02:00
parent 89a3d76122
commit 8ef06cb3e7
21 changed files with 1106 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
aibook.fun {
# AIBook.fun is a static marketing site — pure reverse proxy to the Next.js
# standalone server. No auth/headers added here beyond baseline security.
reverse_proxy web:3000
log {
# Host-bind-mounted /var/log/caddy/ so logs survive container recreation.
output file /var/log/caddy/aibook-fun.log
}
header {
# Force HTTPS for a year (HSTS — a lapsed cert is a service outage, not a
# warning; weown rail per W24 phase4 findings).
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options nosniff
X-Frame-Options SAMEORIGIN
X-XSS-Protection "1; mode=block"
Referrer-Policy strict-origin-when-cross-origin
-Server
}
}
+41
View File
@@ -0,0 +1,41 @@
# syntax=docker/dockerfile:1
# aibook-fun — Next.js 16 marketing site (marketing-site/), standalone output.
#
# Build context = marketing-site/ (see .gitea/workflows/deploy.yml):
# docker build -f deploy/docker/Dockerfile -t <image> marketing-site/
#
# Multi-stage: deps → build (next build → .next/standalone) → slim non-root runner.
# Requires `output: 'standalone'` in next.config.mjs (added on this branch).
# Node 20.9+ / 22+ required by Next 16; pin 22 LTS.
FROM node:22-alpine AS deps
WORKDIR /app
# Lockfiles first for layer caching.
COPY package.json package-lock.json ./
RUN npm ci
FROM node:22-alpine AS builder
WORKDIR /app
ENV NEXT_TELEMETRY_DISABLED=1
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
# Next standalone ships as a single node server.js; run as non-root (uid 1001).
RUN addgroup --system --gid 1001 nodejs \
&& adduser --system --uid 1001 --ingroup nodejs nextjs
# Static assets not bundled into the standalone output.
COPY --from=builder /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
ENV PORT=3000
ENV HOSTNAME=0.0.0.0
CMD ["node", "server.js"]
+59
View File
@@ -0,0 +1,59 @@
services:
web:
image: aibook-fun-marketing:v1
pull_policy: never
restart: unless-stopped
ports:
# Loopback-only: lets caddy proxy and health probes reach the app.
# Not internet-exposed; caddy handles :80/:443 → web:3000.
- "127.0.0.1:3000:3000"
networks:
- aibooknet
healthcheck:
test: ["CMD-SHELL", "curl -f -o /dev/null -w '%{http_code}' http://localhost:3000/ | grep -q '^200$' || exit 1"]
start_period: 30s
interval: 30s
timeout: 10s
retries: 5
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 128M
caddy:
image: caddy:2
restart: unless-stopped
depends_on:
- web
environment:
DOMAIN: aibook.fun
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
- /var/log/caddy:/var/log/caddy
networks:
- aibooknet
deploy:
resources:
limits:
memory: 256M
reservations:
memory: 128M
volumes:
caddy_data:
name: aibook_fun_caddy_data
caddy_config:
name: aibook_fun_caddy_config
networks:
aibooknet:
name: aibook_fun_net
driver: bridge