replace nginx with existing traefik, split networks.

This commit is contained in:
Jake Kasper
2025-08-21 07:23:55 -05:00
parent b3662ea35e
commit 3840b627dc
6 changed files with 64 additions and 193 deletions

View File

@@ -20,14 +20,14 @@ JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
AUTHENTIK_CLIENT_ID=your-authentik-client-id AUTHENTIK_CLIENT_ID=your-authentik-client-id
AUTHENTIK_CLIENT_SECRET=your-authentik-client-secret AUTHENTIK_CLIENT_SECRET=your-authentik-client-secret
AUTHENTIK_BASE_URL=https://your-authentik-domain.com AUTHENTIK_BASE_URL=https://your-authentik-domain.com
AUTHENTIK_CALLBACK_URL=http://localhost:5000/api/auth/authentik/callback AUTHENTIK_CALLBACK_URL=https://turftracker.kaspers.us/api/auth/authentik/callback
# Weather API Configuration # Weather API Configuration
# Get a free API key from https://openweathermap.org/api # Get a free API key from https://openweathermap.org/api
WEATHER_API_KEY=your-openweathermap-api-key WEATHER_API_KEY=your-openweathermap-api-key
# Application URLs # Application URLs (automatically configured for production)
FRONTEND_URL=http://localhost:3000 FRONTEND_URL=https://turftracker.kaspers.us
# Node Environment # Node Environment
NODE_ENV=development NODE_ENV=development

View File

@@ -66,7 +66,7 @@ TurfTracker is a comprehensive web application designed for homeowners to track
- **Frontend**: React 18, Tailwind CSS, React Router, React Query, Leaflet Maps - **Frontend**: React 18, Tailwind CSS, React Router, React Query, Leaflet Maps
- **Backend**: Node.js, Express.js, PostgreSQL - **Backend**: Node.js, Express.js, PostgreSQL
- **Authentication**: JWT, OAuth2 (Authentik) - **Authentication**: JWT, OAuth2 (Authentik)
- **Infrastructure**: Docker, Nginx - **Infrastructure**: Docker, Traefik
- **Maps**: OpenStreetMap via Leaflet, Esri Satellite Imagery - **Maps**: OpenStreetMap via Leaflet, Esri Satellite Imagery
- **APIs**: OpenWeatherMap - **APIs**: OpenWeatherMap
@@ -75,8 +75,18 @@ TurfTracker is a comprehensive web application designed for homeowners to track
### Prerequisites ### Prerequisites
- Docker and Docker Compose - Docker and Docker Compose
- Traefik reverse proxy running with `proxy` network
- DNS pointing `turftracker.kaspers.us` to your server
- Git - Git
### Traefik Configuration Notes
This application is pre-configured for deployment behind Traefik with:
- **Networks**: `proxy` (external) for Traefik, `turftracker` (internal) for service communication
- **SSL/TLS**: Automatic Let's Encrypt certificates via Traefik
- **Security**: Database isolated from public network
- **Routing**: Frontend serves on root, API on `/api` path
### Installation ### Installation
1. **Clone the repository** 1. **Clone the repository**
@@ -123,14 +133,14 @@ TurfTracker is a comprehensive web application designed for homeowners to track
``` ```
4. **Access the application** 4. **Access the application**
- Frontend: http://localhost:3000 - Application: https://turftracker.kaspers.us
- Backend API: http://localhost:5000 - API: https://turftracker.kaspers.us/api
- Database: localhost:5432 - Database: Internal network only (not exposed)
### First Time Setup ### First Time Setup
1. **Create an admin account** 1. **Create an admin account**
- Go to http://localhost:3000/register - Go to https://turftracker.kaspers.us/register
- Register with your email and password - Register with your email and password
- The first user becomes an admin automatically - The first user becomes an admin automatically
@@ -212,7 +222,7 @@ If you have an Authentik instance for SSO:
- Create new "OAuth2/OpenID Provider" - Create new "OAuth2/OpenID Provider"
- Set Authorization grant type: `authorization-code` - Set Authorization grant type: `authorization-code`
- Set Client type: `confidential` - Set Client type: `confidential`
- Set Redirect URIs: `http://localhost:5000/api/auth/authentik/callback` - Set Redirect URIs: `https://turftracker.kaspers.us/api/auth/authentik/callback`
- Note the Client ID and Client Secret - Note the Client ID and Client Secret
2. **In your `.env` file:** 2. **In your `.env` file:**
@@ -220,7 +230,7 @@ If you have an Authentik instance for SSO:
AUTHENTIK_CLIENT_ID=your-client-id-from-authentik AUTHENTIK_CLIENT_ID=your-client-id-from-authentik
AUTHENTIK_CLIENT_SECRET=your-client-secret-from-authentik AUTHENTIK_CLIENT_SECRET=your-client-secret-from-authentik
AUTHENTIK_BASE_URL=https://your-authentik-domain.com AUTHENTIK_BASE_URL=https://your-authentik-domain.com
AUTHENTIK_CALLBACK_URL=http://localhost:5000/api/auth/authentik/callback AUTHENTIK_CALLBACK_URL=https://turftracker.kaspers.us/api/auth/authentik/callback
``` ```
3. **In Authentik Applications:** 3. **In Authentik Applications:**
@@ -252,9 +262,7 @@ turftracker/
│ └── package.json │ └── package.json
├── database/ # PostgreSQL schema ├── database/ # PostgreSQL schema
│ └── init.sql # Database initialization │ └── init.sql # Database initialization
── nginx/ # Reverse proxy configuration ── docker-compose.yml # Container orchestration with Traefik
│ └── nginx.conf
└── docker-compose.yml # Container orchestration
``` ```
## Usage Guide ## Usage Guide

View File

@@ -7,7 +7,7 @@ WORKDIR /app
COPY package*.json ./ COPY package*.json ./
# Install dependencies # Install dependencies
RUN npm ci --only=production RUN npm install --only=production
# Copy source code # Copy source code
COPY . . COPY . .

View File

@@ -1,66 +1,80 @@
version: '3.8' version: '3.8'
networks:
proxy:
external: true
turftracker:
driver: bridge
services: services:
frontend: frontend:
build: build:
context: ./frontend context: ./frontend
dockerfile: Dockerfile dockerfile: Dockerfile
ports:
- "3000:3000"
environment: environment:
- REACT_APP_API_URL=http://localhost:5000 - REACT_APP_API_URL=https://turftracker.kaspers.us
volumes: volumes:
- ./frontend:/app - ./frontend:/app
- /app/node_modules - /app/node_modules
depends_on: depends_on:
- backend - backend
networks:
- proxy
- turftracker
labels:
- "traefik.enable=true"
- "traefik.http.routers.turftracker-frontend.rule=Host(`turftracker.kaspers.us`)"
- "traefik.http.routers.turftracker-frontend.entrypoints=websecure"
- "traefik.http.routers.turftracker-frontend.tls.certresolver=letsencrypt"
- "traefik.http.services.turftracker-frontend.loadbalancer.server.port=3000"
- "traefik.docker.network=proxy"
backend: backend:
build: build:
context: ./backend context: ./backend
dockerfile: Dockerfile dockerfile: Dockerfile
ports:
- "5000:5000"
environment: environment:
- NODE_ENV=development - NODE_ENV=production
- DB_HOST=db - DB_HOST=db
- DB_PORT=5432 - DB_PORT=5432
- DB_NAME=turftracker - DB_NAME=${DB_NAME:-turftracker}
- DB_USER=turftracker - DB_USER=${DB_USER:-turftracker}
- DB_PASSWORD=password123 - DB_PASSWORD=${DB_PASSWORD:-password123}
- JWT_SECRET=your-super-secret-jwt-key - JWT_SECRET=${JWT_SECRET}
- AUTHENTIK_CLIENT_ID=${AUTHENTIK_CLIENT_ID:-} - AUTHENTIK_CLIENT_ID=${AUTHENTIK_CLIENT_ID:-}
- AUTHENTIK_CLIENT_SECRET=${AUTHENTIK_CLIENT_SECRET:-} - AUTHENTIK_CLIENT_SECRET=${AUTHENTIK_CLIENT_SECRET:-}
- AUTHENTIK_BASE_URL=${AUTHENTIK_BASE_URL:-} - AUTHENTIK_BASE_URL=${AUTHENTIK_BASE_URL:-}
- AUTHENTIK_CALLBACK_URL=${AUTHENTIK_CALLBACK_URL:-} - AUTHENTIK_CALLBACK_URL=${AUTHENTIK_CALLBACK_URL:-https://turftracker.kaspers.us/api/auth/authentik/callback}
- WEATHER_API_KEY=${WEATHER_API_KEY:-} - WEATHER_API_KEY=${WEATHER_API_KEY:-}
- FRONTEND_URL=https://turftracker.kaspers.us
volumes: volumes:
- ./backend:/app - ./backend:/app
- /app/node_modules - /app/node_modules
depends_on: depends_on:
- db - db
networks:
- proxy
- turftracker
labels:
- "traefik.enable=true"
- "traefik.http.routers.turftracker-backend.rule=Host(`turftracker.kaspers.us`) && PathPrefix(`/api`)"
- "traefik.http.routers.turftracker-backend.entrypoints=websecure"
- "traefik.http.routers.turftracker-backend.tls.certresolver=letsencrypt"
- "traefik.http.services.turftracker-backend.loadbalancer.server.port=5000"
- "traefik.docker.network=proxy"
db: db:
image: postgres:15-alpine image: postgres:15-alpine
environment: environment:
- POSTGRES_USER=turftracker - POSTGRES_USER=${DB_USER:-turftracker}
- POSTGRES_PASSWORD=password123 - POSTGRES_PASSWORD=${DB_PASSWORD:-password123}
- POSTGRES_DB=turftracker - POSTGRES_DB=${DB_NAME:-turftracker}
ports:
- "5432:5432"
volumes: volumes:
- postgres_data:/var/lib/postgresql/data - postgres_data:/var/lib/postgresql/data
- ./database/init.sql:/docker-entrypoint-initdb.d/init.sql - ./database/init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
nginx: - turftracker
image: nginx:alpine # Database should not be exposed to proxy network for security
ports:
- "80:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
depends_on:
- frontend
- backend
volumes: volumes:
postgres_data: postgres_data:

View File

@@ -7,7 +7,7 @@ WORKDIR /app
COPY package*.json ./ COPY package*.json ./
# Install dependencies # Install dependencies
RUN npm ci RUN npm install
# Copy source code # Copy source code
COPY . . COPY . .
@@ -25,7 +25,7 @@ EXPOSE 3000
# Health check # Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000 || exit 1 CMD wget --no-verbose --tries=1 --spider http://localhost:3000 || exit 1
# Start the application # Start the application
CMD ["npm", "start"] CMD ["npm", "start"]

View File

@@ -1,151 +0,0 @@
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types
application/atom+xml
application/geo+json
application/javascript
application/x-javascript
application/json
application/ld+json
application/manifest+json
application/rdf+xml
application/rss+xml
application/xhtml+xml
application/xml
font/eot
font/otf
font/ttf
image/svg+xml
text/css
text/javascript
text/plain
text/xml;
# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=auth:10m rate=5r/m;
# Security headers
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy "strict-origin-when-cross-origin";
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https://*.openstreetmap.org https://server.arcgisonline.com; connect-src 'self' https://api.openweathermap.org https://*.openstreetmap.org https://server.arcgisonline.com;";
upstream backend {
server backend:5000;
}
upstream frontend {
server frontend:3000;
}
server {
listen 80;
server_name localhost;
# API routes
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://backend;
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;
# CORS headers for API
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept, Authorization";
add_header Access-Control-Allow-Credentials true;
# Handle preflight requests
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept, Authorization";
add_header Access-Control-Allow-Credentials true;
add_header Content-Length 0;
add_header Content-Type text/plain;
return 204;
}
}
# Auth routes with stricter rate limiting
location /api/auth/ {
limit_req zone=auth burst=5 nodelay;
proxy_pass http://backend;
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 {
proxy_pass http://backend;
access_log off;
}
# Static files and React app
location / {
proxy_pass http://frontend;
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;
# Handle React Router
try_files $uri $uri/ @fallback;
}
# Fallback for React Router (SPA)
location @fallback {
proxy_pass http://frontend;
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;
}
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
proxy_pass http://frontend;
expires 1y;
add_header Cache-Control "public, immutable";
}
# Security - deny access to sensitive files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
location ~ ~$ {
deny all;
access_log off;
log_not_found off;
}
}
}