diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1d27032 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,40 @@ +# Root .dockerignore for the entire solution + +# Git +.git/ +.gitignore + +# Docker files +Dockerfile +docker-compose* +.dockerignore + +# IDE +.idea/ +.vs/ +.vscode/ +*.suo +*.user + +# OS +.DS_Store +Thumbs.db + +# Build output +**/bin/ +**/obj/ +**/dist/ + +# Node modules +**/node_modules/ + +# Logs +*.log + +# Test results +**/TestResults/ + +# Secrets +**/appsettings.Development.json +**/secrets.json +**/.env* diff --git a/GermanApp/.dockerignore b/GermanApp/.dockerignore new file mode 100644 index 0000000..19c4d69 --- /dev/null +++ b/GermanApp/.dockerignore @@ -0,0 +1,31 @@ +# .NET Core +**/bin/ +**/obj/ + +# User secrets +**/appsettings.Development.json +**/secrets.json + +# NuGet packages +**/packages/ + +# IDE +.idea/ +.vs/ +*.user +*.suo + +# Git +.git/ +.gitignore + +# Docker +Dockerfile +.dockerignore + +# OS +.DS_Store +Thumbs.db + +# Test results +**/TestResults/ diff --git a/GermanApp/Dockerfile b/GermanApp/Dockerfile new file mode 100644 index 0000000..8a58065 --- /dev/null +++ b/GermanApp/Dockerfile @@ -0,0 +1,54 @@ +# GermanApp Backend Dockerfile +# .NET 9.0 Web API Application +# Multi-stage build for production optimization + +# ============================================ +# Build Stage +# ============================================ +FROM mcr.microsoft.com/dotnet/sdk:9.0.204-alpine3.19 AS build +WORKDIR /src + +# Copy project file and restore dependencies +COPY ["GermanApp/GermanApp.csproj", "GermanApp/"] +RUN dotnet restore "GermanApp/GermanApp.csproj" + +# Copy everything else and build +COPY . . +WORKDIR "/src/GermanApp" +RUN dotnet build "GermanApp.csproj" -c Release -o /app/build + +# Publish the application +RUN dotnet publish "GermanApp.csproj" -c Release -o /app/publish \ + --no-restore \ + -p:PublishReadyToRun=true \ + -p:PublishSingleFile=false \ + -p:PublishTrimmed=true + +# ============================================ +# Publish Stage +# ============================================ +FROM build AS publish + +# ============================================ +# Runtime Stage +# ============================================ +FROM mcr.microsoft.com/dotnet/aspnet:9.0.4-alpine3.19 AS runtime +WORKDIR /app + +# Copy published app from publish stage +COPY --from=publish /app/publish . + +# Set environment variables +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false +ENV ASPNETCORE_URLS=http://+:8080 +ENV ASPNETCORE_ENVIRONMENT=Production + +# Expose port +EXPOSE 8080 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:8080/health || exit 1 + +# Entry point +ENTRYPOINT ["dotnet", "GermanApp.dll"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..704504d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,68 @@ +version: '3.8' + +services: + # PostgreSQL Database + db: + image: postgres:15-alpine + container_name: deutschlernen-db + environment: + POSTGRES_DB: DeutschLernen + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + volumes: + - postgres_data:/var/lib/postgresql/data + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres -d DeutschLernen"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + + # Backend API + backend: + build: + context: ./GermanApp + dockerfile: Dockerfile + container_name: deutschlernen-backend + environment: + ASPNETCORE_ENVIRONMENT: Development + ASPNETCORE_URLS: http://+:8080 + ConnectionStrings__DefaultConnection: Host=db;Port=5432;Database=DeutschLernen;Username=postgres;Password=postgres + depends_on: + db: + condition: service_healthy + ports: + - "8080:8080" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s + restart: unless-stopped + + # Frontend + frontend: + build: + context: ./german-app-frontend + dockerfile: Dockerfile + container_name: deutschlernen-frontend + environment: + NODE_ENV: production + depends_on: + backend: + condition: service_healthy + ports: + - "3000:3000" + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s + restart: unless-stopped + +volumes: + postgres_data: diff --git a/german-app-frontend/.dockerignore b/german-app-frontend/.dockerignore new file mode 100644 index 0000000..2366ff5 --- /dev/null +++ b/german-app-frontend/.dockerignore @@ -0,0 +1,41 @@ +# Node modules +node_modules/ + +# npm cache +npm-cache/ + +# Dist directory (will be built in container) +dist/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Git +.git/ +.gitignore + +# Docker +Dockerfile +.dockerignore + +# Logs +*.log +npm-debug.log* + +# Environment files +.env +.env.local +.env.*.local + +# Build output +build/ + +# Test coverage +coverage/ diff --git a/german-app-frontend/Dockerfile b/german-app-frontend/Dockerfile new file mode 100644 index 0000000..a895b6f --- /dev/null +++ b/german-app-frontend/Dockerfile @@ -0,0 +1,43 @@ +# GermanApp Frontend Dockerfile +# React 19 + TypeScript + Vite Application +# Multi-stage build for production optimization + +# ============================================ +# Build Stage +# ============================================ +FROM node:20-alpine AS build +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm ci + +# Copy source files +COPY . . + +# Build the application +RUN npm run build + +# ============================================ +# Runtime Stage +# ============================================ +FROM nginx:alpine AS runtime +WORKDIR /usr/share/nginx/html + +# Copy built files from build stage +COPY --from=build /app/dist . + +# Copy nginx configuration +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# Expose port +EXPOSE 3000 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:3000/ || exit 1 + +# Entry point (nginx runs by default) +CMD ["nginx", "-g", "daemon off;"] diff --git a/german-app-frontend/nginx.conf b/german-app-frontend/nginx.conf new file mode 100644 index 0000000..9446ba8 --- /dev/null +++ b/german-app-frontend/nginx.conf @@ -0,0 +1,41 @@ +server { + listen 3000; + server_name localhost; + + # Root directory + root /usr/share/nginx/html; + index index.html; + + # Handle React Router - return index.html for all requests + location / { + try_files $uri $uri/ /index.html; + } + + # API proxy to backend (when running in Docker Compose) + location /api/ { + proxy_pass http://backend:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + + # Error pages + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } +}