feat(01-01): Next.js app + Docker Compose stack with network segmentation

- Next.js 15 app with Tailwind CSS v4, standalone output for Docker
- Root layout with de locale, page with Tessera placeholder
- Multi-stage Dockerfile for web with monorepo root context
- Docker Compose with 4 services: traefik, web, api, db
- Three segregated networks: frontend-net, backend-net, data-net (internal)
- Traefik v2.11 reverse proxy routing / to web, /api to api
- API strip prefix middleware for clean routing
- PostgreSQL 16-alpine with health checks and named volume
- Fixed API Dockerfile for pnpm monorepo node_modules structure
- Fixed Next.js standalone path for monorepo (apps/web/server.js)
- Fixed Traefik Docker API version compatibility
- Network segmentation: web NOT on data-net, api NOT on frontend-net
This commit is contained in:
2026-06-18 10:17:21 +02:00
parent 04c78b4bdc
commit 2c12878cb1
12 changed files with 1079 additions and 6 deletions
+9 -6
View File
@@ -16,15 +16,18 @@ COPY packages/shared/ ./packages/shared/
COPY tsconfig.base.json ./
RUN pnpm --filter=@tessera/api build
FROM node:24-alpine AS runner
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nestjs && \
adduser --system --uid 1001 nestjs
COPY --from=builder /app/apps/api/dist ./dist
COPY --from=builder /app/apps/api/node_modules ./node_modules
COPY --from=builder /app/apps/api/package.json ./
COPY --from=builder /app/node_modules/.pnpm ./node_modules/.pnpm
COPY --from=deps /app/package.json /app/pnpm-workspace.yaml /app/pnpm-lock.yaml ./
COPY --from=deps /app/node_modules ./node_modules
COPY --from=deps /app/apps/api/node_modules ./apps/api/node_modules
COPY --from=deps /app/apps/api/package.json ./apps/api/
COPY --from=deps /app/packages/shared/package.json ./packages/shared/
COPY --from=builder /app/apps/api/dist ./apps/api/dist
COPY --from=builder /app/packages/shared/src ./packages/shared/src
USER nestjs
EXPOSE 3001
CMD ["node", "dist/main.js"]
CMD ["node", "apps/api/dist/main.js"]
+29
View File
@@ -0,0 +1,29 @@
FROM node:24-alpine AS base
RUN corepack enable && corepack prepare pnpm@9 --activate
FROM base AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY apps/web/package.json ./apps/web/
COPY packages/shared/package.json ./packages/shared/
RUN pnpm install --frozen-lockfile --filter=@tessera/web...
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/ ./
COPY apps/web/ ./apps/web/
COPY packages/shared/ ./packages/shared/
COPY tsconfig.base.json ./
RUN pnpm --filter=@tessera/web build
FROM node:24-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs
COPY --from=builder /app/apps/web/public ./apps/web/public
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
USER nextjs
EXPOSE 3000
CMD ["node", "apps/web/server.js"]
+7
View File
@@ -0,0 +1,7 @@
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
output: 'standalone' as const,
};
export default nextConfig;
+24
View File
@@ -0,0 +1,24 @@
{
"name": "@tessera/web",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"type-check": "tsc --noEmit"
},
"dependencies": {
"next": "^15.3.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"tailwindcss": "^4.0.0",
"@tailwindcss/postcss": "^4.0.0",
"postcss": "^8.0.0",
"typescript": "^5.5.0",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0"
}
}
+8
View File
@@ -0,0 +1,8 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
"@tailwindcss/postcss": {},
},
};
export default config;
+1
View File
@@ -0,0 +1 @@
@import "tailwindcss";
+21
View File
@@ -0,0 +1,21 @@
import type { Metadata } from 'next';
import './globals.css';
export const metadata: Metadata = {
title: 'Tessera',
description: 'Modulare Workflow-Plattform',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="de">
<body className="antialiased">
{children}
</body>
</html>
);
}
+10
View File
@@ -0,0 +1,10 @@
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-center">
<h1 className="text-4xl font-bold">Tessera</h1>
<p className="mt-4 text-lg text-gray-600">
Portal wird geladen...
</p>
</main>
);
}
+18
View File
@@ -0,0 +1,18 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "preserve",
"lib": ["dom", "dom.iterable", "esnext"],
"module": "esnext",
"moduleResolution": "bundler",
"allowJs": true,
"noEmit": true,
"incremental": true,
"plugins": [{ "name": "next" }],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}