replace nginx with existing traefik, split networks.
This commit is contained in:
@@ -20,14 +20,14 @@ JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
|
||||
AUTHENTIK_CLIENT_ID=your-authentik-client-id
|
||||
AUTHENTIK_CLIENT_SECRET=your-authentik-client-secret
|
||||
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
|
||||
# Get a free API key from https://openweathermap.org/api
|
||||
WEATHER_API_KEY=your-openweathermap-api-key
|
||||
|
||||
# Application URLs
|
||||
FRONTEND_URL=http://localhost:3000
|
||||
# Application URLs (automatically configured for production)
|
||||
FRONTEND_URL=https://turftracker.kaspers.us
|
||||
|
||||
# Node Environment
|
||||
NODE_ENV=development
|
||||
|
||||
28
README.md
28
README.md
@@ -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
|
||||
- **Backend**: Node.js, Express.js, PostgreSQL
|
||||
- **Authentication**: JWT, OAuth2 (Authentik)
|
||||
- **Infrastructure**: Docker, Nginx
|
||||
- **Infrastructure**: Docker, Traefik
|
||||
- **Maps**: OpenStreetMap via Leaflet, Esri Satellite Imagery
|
||||
- **APIs**: OpenWeatherMap
|
||||
|
||||
@@ -75,8 +75,18 @@ TurfTracker is a comprehensive web application designed for homeowners to track
|
||||
### Prerequisites
|
||||
|
||||
- Docker and Docker Compose
|
||||
- Traefik reverse proxy running with `proxy` network
|
||||
- DNS pointing `turftracker.kaspers.us` to your server
|
||||
- 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
|
||||
|
||||
1. **Clone the repository**
|
||||
@@ -123,14 +133,14 @@ TurfTracker is a comprehensive web application designed for homeowners to track
|
||||
```
|
||||
|
||||
4. **Access the application**
|
||||
- Frontend: http://localhost:3000
|
||||
- Backend API: http://localhost:5000
|
||||
- Database: localhost:5432
|
||||
- Application: https://turftracker.kaspers.us
|
||||
- API: https://turftracker.kaspers.us/api
|
||||
- Database: Internal network only (not exposed)
|
||||
|
||||
### First Time Setup
|
||||
|
||||
1. **Create an admin account**
|
||||
- Go to http://localhost:3000/register
|
||||
- Go to https://turftracker.kaspers.us/register
|
||||
- Register with your email and password
|
||||
- The first user becomes an admin automatically
|
||||
|
||||
@@ -212,7 +222,7 @@ If you have an Authentik instance for SSO:
|
||||
- Create new "OAuth2/OpenID Provider"
|
||||
- Set Authorization grant type: `authorization-code`
|
||||
- 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
|
||||
|
||||
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_SECRET=your-client-secret-from-authentik
|
||||
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:**
|
||||
@@ -252,9 +262,7 @@ turftracker/
|
||||
│ └── package.json
|
||||
├── database/ # PostgreSQL schema
|
||||
│ └── init.sql # Database initialization
|
||||
├── nginx/ # Reverse proxy configuration
|
||||
│ └── nginx.conf
|
||||
└── docker-compose.yml # Container orchestration
|
||||
└── docker-compose.yml # Container orchestration with Traefik
|
||||
```
|
||||
|
||||
## Usage Guide
|
||||
|
||||
@@ -7,7 +7,7 @@ WORKDIR /app
|
||||
COPY package*.json ./
|
||||
|
||||
# Install dependencies
|
||||
RUN npm ci --only=production
|
||||
RUN npm install --only=production
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
@@ -1,66 +1,80 @@
|
||||
version: '3.8'
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
external: true
|
||||
turftracker:
|
||||
driver: bridge
|
||||
|
||||
services:
|
||||
frontend:
|
||||
build:
|
||||
context: ./frontend
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- REACT_APP_API_URL=http://localhost:5000
|
||||
- REACT_APP_API_URL=https://turftracker.kaspers.us
|
||||
volumes:
|
||||
- ./frontend:/app
|
||||
- /app/node_modules
|
||||
depends_on:
|
||||
- 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:
|
||||
build:
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "5000:5000"
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
- NODE_ENV=production
|
||||
- DB_HOST=db
|
||||
- DB_PORT=5432
|
||||
- DB_NAME=turftracker
|
||||
- DB_USER=turftracker
|
||||
- DB_PASSWORD=password123
|
||||
- JWT_SECRET=your-super-secret-jwt-key
|
||||
- DB_NAME=${DB_NAME:-turftracker}
|
||||
- DB_USER=${DB_USER:-turftracker}
|
||||
- DB_PASSWORD=${DB_PASSWORD:-password123}
|
||||
- JWT_SECRET=${JWT_SECRET}
|
||||
- AUTHENTIK_CLIENT_ID=${AUTHENTIK_CLIENT_ID:-}
|
||||
- AUTHENTIK_CLIENT_SECRET=${AUTHENTIK_CLIENT_SECRET:-}
|
||||
- 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:-}
|
||||
- FRONTEND_URL=https://turftracker.kaspers.us
|
||||
volumes:
|
||||
- ./backend:/app
|
||||
- /app/node_modules
|
||||
depends_on:
|
||||
- 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:
|
||||
image: postgres:15-alpine
|
||||
environment:
|
||||
- POSTGRES_USER=turftracker
|
||||
- POSTGRES_PASSWORD=password123
|
||||
- POSTGRES_DB=turftracker
|
||||
ports:
|
||||
- "5432:5432"
|
||||
- POSTGRES_USER=${DB_USER:-turftracker}
|
||||
- POSTGRES_PASSWORD=${DB_PASSWORD:-password123}
|
||||
- POSTGRES_DB=${DB_NAME:-turftracker}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./database/init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
|
||||
depends_on:
|
||||
- frontend
|
||||
- backend
|
||||
networks:
|
||||
- turftracker
|
||||
# Database should not be exposed to proxy network for security
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
@@ -7,7 +7,7 @@ WORKDIR /app
|
||||
COPY package*.json ./
|
||||
|
||||
# Install dependencies
|
||||
RUN npm ci
|
||||
RUN npm install
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
@@ -25,7 +25,7 @@ EXPOSE 3000
|
||||
|
||||
# Health check
|
||||
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
|
||||
CMD ["npm", "start"]
|
||||
151
nginx/nginx.conf
151
nginx/nginx.conf
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user