deploy
This commit is contained in:
10
.dockerignore
Normal file
10
.dockerignore
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
node_modules
|
||||||
|
.next
|
||||||
|
.git
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
|
*.md
|
||||||
|
docs
|
||||||
|
reference
|
||||||
|
scripts
|
||||||
|
public/datasheets/*.pdf
|
||||||
74
.gitea/workflows/deploy.yml
Normal file
74
.gitea/workflows/deploy.yml
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
name: Build & Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: docker
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# --- Tools ---
|
||||||
|
- name: Install tools
|
||||||
|
run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y \
|
||||||
|
git \
|
||||||
|
docker.io \
|
||||||
|
openssh-client \
|
||||||
|
rsync
|
||||||
|
|
||||||
|
# --- Checkout ---
|
||||||
|
- name: Checkout repo
|
||||||
|
run: |
|
||||||
|
git clone https://git.infra.mintel.me/mintel/klz-cables.git .
|
||||||
|
git checkout main
|
||||||
|
|
||||||
|
# --- Docker registry login ---
|
||||||
|
- name: Login to registry
|
||||||
|
env:
|
||||||
|
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
|
||||||
|
REGISTRY_PASS: ${{ secrets.REGISTRY_PASS }}
|
||||||
|
run: |
|
||||||
|
echo "$REGISTRY_PASS" | docker login registry.infra.mintel.me \
|
||||||
|
-u "$REGISTRY_USER" \
|
||||||
|
--password-stdin
|
||||||
|
|
||||||
|
# --- Build image ---
|
||||||
|
- name: Build image
|
||||||
|
run: |
|
||||||
|
docker build \
|
||||||
|
-t registry.infra.mintel.me/mintel/klz-cables:latest .
|
||||||
|
|
||||||
|
# --- Push image ---
|
||||||
|
- name: Push image
|
||||||
|
run: |
|
||||||
|
docker push registry.infra.mintel.me/mintel/klz-cables:latest
|
||||||
|
|
||||||
|
# --- SSH setup ---
|
||||||
|
- name: Setup SSH
|
||||||
|
env:
|
||||||
|
ALPHA_SSH_KEY: ${{ secrets.ALPHA_SSH_KEY }}
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
echo "$ALPHA_SSH_KEY" > ~/.ssh/id_ed25519
|
||||||
|
chmod 600 ~/.ssh/id_ed25519
|
||||||
|
ssh-keyscan -H alpha.mintel.me >> ~/.ssh/known_hosts
|
||||||
|
|
||||||
|
# --- Sync compose (yml OR yaml) ---
|
||||||
|
- name: Sync compose file
|
||||||
|
run: |
|
||||||
|
rsync -av ./docker-compose.y*ml \
|
||||||
|
deploy@alpha.mintel.me:/home/deploy/sites/klz-cables/
|
||||||
|
|
||||||
|
# --- Deploy ---
|
||||||
|
- name: Deploy on server
|
||||||
|
run: |
|
||||||
|
ssh deploy@alpha.mintel.me '
|
||||||
|
cd /home/deploy/sites/klz-cables &&
|
||||||
|
docker compose -f docker-compose.yml pull 2>/dev/null ||
|
||||||
|
docker compose -f docker-compose.yaml pull &&
|
||||||
|
docker compose up -d
|
||||||
|
'
|
||||||
59
Dockerfile
Normal file
59
Dockerfile
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
FROM node:20-alpine AS base
|
||||||
|
|
||||||
|
# Install dependencies only when needed
|
||||||
|
FROM base AS deps
|
||||||
|
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
||||||
|
RUN apk add --no-cache libc6-compat
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install dependencies based on the preferred package manager
|
||||||
|
COPY package.json package-lock.json* ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
|
||||||
|
# Rebuild the source code only when needed
|
||||||
|
FROM base AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Next.js collects completely anonymous telemetry data about general usage.
|
||||||
|
# Learn more here: https://nextjs.org/telemetry
|
||||||
|
# Uncomment the following line in case you want to disable telemetry during the build.
|
||||||
|
# ENV NEXT_TELEMETRY_DISABLED 1
|
||||||
|
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Production image, copy all the files and run next
|
||||||
|
FROM base AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV NODE_ENV production
|
||||||
|
# Uncomment the following line in case you want to disable telemetry during runtime.
|
||||||
|
# ENV NEXT_TELEMETRY_DISABLED 1
|
||||||
|
|
||||||
|
RUN addgroup --system --gid 1001 nodejs
|
||||||
|
RUN adduser --system --uid 1001 nextjs
|
||||||
|
|
||||||
|
COPY --from=builder /app/public ./public
|
||||||
|
|
||||||
|
# Set the correct permission for prerender cache
|
||||||
|
RUN mkdir .next
|
||||||
|
RUN chown nextjs:nodejs .next
|
||||||
|
|
||||||
|
# Automatically leverage output traces to reduce image size
|
||||||
|
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||||
|
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
|
||||||
|
# set hostname to localhost
|
||||||
|
ENV HOSTNAME "0.0.0.0"
|
||||||
|
|
||||||
|
# server.js is created by next build from the standalone output
|
||||||
|
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
|
||||||
|
CMD ["node", "server.js"]
|
||||||
5
app/health/route.ts
Normal file
5
app/health/route.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
return new Response('OK', { status: 200 });
|
||||||
|
}
|
||||||
22
docker-compose.yml
Normal file
22
docker-compose.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: registry.infra.mintel.me/mintel/klz-cables:latest
|
||||||
|
restart: always
|
||||||
|
networks:
|
||||||
|
- traefik
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.klz-cables.rule=Host(klz-cables.com,www.klz-cables.com)"
|
||||||
|
- "traefik.http.routers.klz-cables.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.klz-cables.tls.certresolver=le"
|
||||||
|
- "traefik.http.services.klz-cables.loadbalancer.server.port=3000"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
|
|
||||||
|
networks:
|
||||||
|
traefik:
|
||||||
|
external: true
|
||||||
195
docs/PLATFORM.md
Normal file
195
docs/PLATFORM.md
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
# Mintel Alpha Platform — Developer Cheat Sheet
|
||||||
|
|
||||||
|
This platform runs real customer websites on their own domains
|
||||||
|
(e.g. klz-cables.com, marisas.world, shop.customer.de).
|
||||||
|
|
||||||
|
You do not manage servers.
|
||||||
|
You ship Docker containers.
|
||||||
|
Mintel runs the platform.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Control Plane (Infra)
|
||||||
|
|
||||||
|
Internal services (developers only):
|
||||||
|
|
||||||
|
Git (Gitea)
|
||||||
|
https://git.infra.mintel.me
|
||||||
|
|
||||||
|
CI (Gitea Actions)
|
||||||
|
https://git.infra.mintel.me/actions
|
||||||
|
|
||||||
|
Container Registry
|
||||||
|
https://registry.infra.mintel.me
|
||||||
|
|
||||||
|
Error Tracking (GlitchTip)
|
||||||
|
https://errors.infra.mintel.me
|
||||||
|
|
||||||
|
Analytics (Umami)
|
||||||
|
https://analytics.infra.mintel.me
|
||||||
|
|
||||||
|
Uptime
|
||||||
|
https://status.infra.mintel.me
|
||||||
|
|
||||||
|
Logs (Dozzle)
|
||||||
|
https://logs.infra.mintel.me
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Production Platform (Alpha)
|
||||||
|
|
||||||
|
Alpha runs all customer websites and is publicly reachable.
|
||||||
|
|
||||||
|
- Listens on ports 80 / 443
|
||||||
|
- Runs Traefik
|
||||||
|
- Routes real domains
|
||||||
|
- Is isolated from Infra
|
||||||
|
|
||||||
|
Customer DNS A records point to the Alpha server IP.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Routing (Traefik)
|
||||||
|
|
||||||
|
Routing is host-based.
|
||||||
|
|
||||||
|
Each service declares its domains via labels:
|
||||||
|
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.app.rule=Host(example.com,www.example.com)
|
||||||
|
- traefik.http.routers.app.entrypoints=websecure
|
||||||
|
- traefik.http.routers.app.tls.certresolver=le
|
||||||
|
- traefik.http.services.app.loadbalancer.server.port=3000
|
||||||
|
|
||||||
|
Traefik:
|
||||||
|
- terminates TLS
|
||||||
|
- auto-issues certificates
|
||||||
|
- supports zero-downtime deploys
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Directory layout on Alpha
|
||||||
|
|
||||||
|
Each app lives in:
|
||||||
|
|
||||||
|
/opt/alpha/sites/APP_NAME
|
||||||
|
|
||||||
|
Contains:
|
||||||
|
|
||||||
|
docker-compose.yml
|
||||||
|
.env (optional)
|
||||||
|
content/
|
||||||
|
db/
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Container Images
|
||||||
|
|
||||||
|
All production images are built by CI and pushed to the Mintel Registry.
|
||||||
|
|
||||||
|
Registry:
|
||||||
|
registry.infra.mintel.me
|
||||||
|
|
||||||
|
Naming:
|
||||||
|
registry.infra.mintel.me/ORG/APP_NAME:TAG
|
||||||
|
|
||||||
|
Example:
|
||||||
|
registry.infra.mintel.me/mintel/mb-grid-solutions:latest
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Databases
|
||||||
|
|
||||||
|
### Postgres (shared)
|
||||||
|
|
||||||
|
One Postgres server, many databases.
|
||||||
|
|
||||||
|
Connection format:
|
||||||
|
postgres://infra:infra@postgres:5432/APP_DB
|
||||||
|
|
||||||
|
Each app must use its own database.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Redis (shared)
|
||||||
|
|
||||||
|
One Redis instance, multiple DB indexes.
|
||||||
|
|
||||||
|
redis://redis:6379/1
|
||||||
|
redis://redis:6379/2
|
||||||
|
|
||||||
|
Each app must use its own DB number.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Health checks (required)
|
||||||
|
|
||||||
|
Every public service must expose:
|
||||||
|
|
||||||
|
GET /health → 200 OK when ready
|
||||||
|
|
||||||
|
Used by Traefik for zero-downtime routing.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Error Tracking (GlitchTip)
|
||||||
|
|
||||||
|
Each app gets a DSN:
|
||||||
|
|
||||||
|
https://PUBLIC_KEY@errors.infra.mintel.me/PROJECT_ID
|
||||||
|
|
||||||
|
Set as:
|
||||||
|
SENTRY_DSN
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Analytics (Umami)
|
||||||
|
|
||||||
|
Each site gets a website ID.
|
||||||
|
|
||||||
|
Include:
|
||||||
|
|
||||||
|
https://analytics.infra.mintel.me/script.js
|
||||||
|
data-website-id=YOUR_ID
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deployment (Gitea Actions)
|
||||||
|
|
||||||
|
Flow:
|
||||||
|
|
||||||
|
- Push to main
|
||||||
|
- CI builds image
|
||||||
|
- Image pushed to registry
|
||||||
|
- Alpha pulls and runs
|
||||||
|
- Traefik routes traffic
|
||||||
|
|
||||||
|
Deploy target:
|
||||||
|
deploy@alpha.mintel.me
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Monitoring
|
||||||
|
|
||||||
|
Errors → GlitchTip
|
||||||
|
Traffic → Umami
|
||||||
|
Logs → Dozzle
|
||||||
|
Uptime → Uptime-Kuma
|
||||||
|
|
||||||
|
Infra monitors everything.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
You push code
|
||||||
|
CI builds images
|
||||||
|
Registry stores images
|
||||||
|
Alpha runs containers
|
||||||
|
Traefik routes domains
|
||||||
|
Databases are shared but isolated
|
||||||
|
Deploys are zero-downtime
|
||||||
|
Everything is monitored
|
||||||
|
|
||||||
|
This is a real production platform.
|
||||||
@@ -4,6 +4,7 @@ const withNextIntl = createNextIntlPlugin();
|
|||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
|
output: 'standalone',
|
||||||
images: {
|
images: {
|
||||||
remotePatterns: [
|
remotePatterns: [
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user