Rewrite docker-compose for staging; fix uploads proxy to use BACKEND_URL HTTP
This commit is contained in:
parent
50a54255a8
commit
beaaf1b99a
2 changed files with 36 additions and 158 deletions
|
|
@ -1,67 +1,53 @@
|
||||||
version: '3.8'
|
version: '3.8'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
# Frontend service
|
|
||||||
frontend:
|
frontend:
|
||||||
build:
|
build:
|
||||||
context: ./frontend
|
context: ./frontend
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
user: node
|
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
backend:
|
||||||
env_file: ./frontend/.env
|
condition: service_healthy
|
||||||
volumes:
|
environment:
|
||||||
- ./logs:/app/logs:rw
|
- BACKEND_URL=http://backend:4000
|
||||||
|
- NODE_ENV=production
|
||||||
networks:
|
networks:
|
||||||
- app-network
|
- app-network
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
test: ["CMD", "wget", "-qO-", "http://localhost:3000"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 512M
|
|
||||||
cpus: '0.5'
|
|
||||||
|
|
||||||
# Backend API service
|
|
||||||
backend:
|
backend:
|
||||||
build:
|
build:
|
||||||
context: ./backend
|
context: ./backend
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
user: node
|
|
||||||
ports:
|
ports:
|
||||||
- "4000:4000"
|
- "4000:4000"
|
||||||
depends_on:
|
depends_on:
|
||||||
- mongodb
|
mongodb:
|
||||||
- redis
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
env_file: ./backend/.env
|
env_file: ./backend/.env
|
||||||
volumes:
|
volumes:
|
||||||
- ./uploads:/app/uploads:rw
|
- ./uploads:/app/uploads:rw
|
||||||
- ./logs:/app/logs:rw
|
|
||||||
networks:
|
networks:
|
||||||
- app-network
|
- app-network
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:4000/health"]
|
test: ["CMD", "wget", "-qO-", "http://localhost:4000/health"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 512M
|
|
||||||
cpus: '0.5'
|
|
||||||
|
|
||||||
# MongoDB service
|
|
||||||
mongodb:
|
mongodb:
|
||||||
image: mongo:7.0
|
image: mongo:7.0
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
user: mongodb
|
|
||||||
volumes:
|
volumes:
|
||||||
- mongo-data:/data/db:rw
|
- mongo-data:/data/db:rw
|
||||||
- ./mongo-init:/docker-entrypoint-initdb.d:ro
|
- ./mongo-init:/docker-entrypoint-initdb.d:ro
|
||||||
|
|
@ -71,84 +57,32 @@ services:
|
||||||
environment:
|
environment:
|
||||||
- MONGO_INITDB_ROOT_USERNAME=${MONGO_USER}
|
- MONGO_INITDB_ROOT_USERNAME=${MONGO_USER}
|
||||||
- MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD}
|
- MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD}
|
||||||
command: ["--auth", "--bind_ip_all", "--tlsMode", "preferTLS"]
|
command: ["--auth", "--bind_ip_all"]
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: echo 'db.runCommand("ping").ok' | mongosh localhost:27017 --quiet
|
test: echo 'db.runCommand("ping").ok' | mongosh localhost:27017 --quiet
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 5
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 1G
|
|
||||||
cpus: '1.0'
|
|
||||||
|
|
||||||
# Redis service
|
|
||||||
redis:
|
redis:
|
||||||
image: redis:alpine
|
image: redis:alpine
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}"]
|
command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}"]
|
||||||
|
env_file: ./backend/.env
|
||||||
volumes:
|
volumes:
|
||||||
- redis-data:/data:rw
|
- redis-data:/data:rw
|
||||||
networks:
|
networks:
|
||||||
- app-network
|
- app-network
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "redis-cli", "ping"]
|
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 256M
|
|
||||||
cpus: '0.2'
|
|
||||||
|
|
||||||
# Nginx service for production
|
|
||||||
nginx:
|
|
||||||
image: nginx:alpine
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- "80:80"
|
|
||||||
- "443:443"
|
|
||||||
volumes:
|
|
||||||
- ./nginx/conf:/etc/nginx/conf.d:ro
|
|
||||||
- ./nginx/ssl:/etc/nginx/ssl:ro
|
|
||||||
- ./frontend/public:/var/www/html:ro
|
|
||||||
depends_on:
|
|
||||||
- frontend
|
|
||||||
- backend
|
|
||||||
networks:
|
|
||||||
- app-network
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "curl", "-f", "https://localhost"]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 3
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 128M
|
|
||||||
cpus: '0.1'
|
|
||||||
|
|
||||||
# Security scanner
|
|
||||||
security_scanner:
|
|
||||||
image: aquasec/trivy
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
|
||||||
- ./security-reports:/reports
|
|
||||||
command: ["image", "--format", "table", "--output", "/reports/scan-$(date +%Y%m%d).txt", "lad-website-frontend:latest", "lad-website-backend:latest"]
|
|
||||||
profiles:
|
|
||||||
- security
|
|
||||||
|
|
||||||
# Networks with isolation
|
|
||||||
networks:
|
networks:
|
||||||
app-network:
|
app-network:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
ipam:
|
|
||||||
config:
|
|
||||||
- subnet: 172.20.0.0/24
|
|
||||||
|
|
||||||
# Volumes with backup capability
|
|
||||||
volumes:
|
volumes:
|
||||||
mongo-data:
|
mongo-data:
|
||||||
name: lad-mongo-data
|
name: lad-mongo-data
|
||||||
|
|
|
||||||
|
|
@ -1,91 +1,35 @@
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* API route to serve uploaded files
|
|
||||||
* This allows us to serve files from the backend uploads directory
|
|
||||||
*/
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
request: NextRequest,
|
request: NextRequest,
|
||||||
{ params }: { params: Promise<{ path: string[] }> }
|
{ params }: { params: Promise<{ path: string[] }> }
|
||||||
) {
|
) {
|
||||||
|
const { path: pathSegments } = await params;
|
||||||
|
const filePath = pathSegments.join('/').replace(/^uploads\//, '');
|
||||||
|
const backendUrl = process.env.BACKEND_URL ?? 'http://localhost:4000';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { path: pathSegments } = await params;
|
const upstream = await fetch(`${backendUrl}/uploads/${filePath}`, {
|
||||||
const filePath = pathSegments.join('/');
|
next: { revalidate: 86400 },
|
||||||
|
});
|
||||||
// Construct the absolute path to the file
|
|
||||||
// This assumes the backend is in the same directory as the frontend
|
if (!upstream.ok) {
|
||||||
// Check if filePath already starts with 'uploads/'
|
return new NextResponse('File not found', { status: upstream.status });
|
||||||
const absolutePath = filePath.startsWith('uploads/')
|
|
||||||
? path.join(process.cwd(), '..', 'backend', filePath)
|
|
||||||
: path.join(process.cwd(), '..', 'backend', 'uploads', filePath);
|
|
||||||
|
|
||||||
console.log('Requested file path:', filePath);
|
|
||||||
console.log('Absolute path:', absolutePath);
|
|
||||||
|
|
||||||
// Check if the file exists
|
|
||||||
if (!fs.existsSync(absolutePath)) {
|
|
||||||
return new NextResponse('File not found', { status: 404 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the file
|
const buffer = await upstream.arrayBuffer();
|
||||||
const fileBuffer = fs.readFileSync(absolutePath);
|
const contentType = upstream.headers.get('content-type') ?? 'application/octet-stream';
|
||||||
|
const filename = path.basename(filePath);
|
||||||
// Determine content type based on file extension
|
|
||||||
const ext = path.extname(filePath).toLowerCase();
|
return new NextResponse(buffer, {
|
||||||
let contentType = 'application/octet-stream'; // Default content type
|
|
||||||
|
|
||||||
// Set content type based on file extension
|
|
||||||
switch (ext) {
|
|
||||||
case '.mp4':
|
|
||||||
contentType = 'video/mp4';
|
|
||||||
break;
|
|
||||||
case '.webm':
|
|
||||||
contentType = 'video/webm';
|
|
||||||
break;
|
|
||||||
case '.jpg':
|
|
||||||
case '.jpeg':
|
|
||||||
contentType = 'image/jpeg';
|
|
||||||
break;
|
|
||||||
case '.png':
|
|
||||||
contentType = 'image/png';
|
|
||||||
break;
|
|
||||||
case '.gif':
|
|
||||||
contentType = 'image/gif';
|
|
||||||
break;
|
|
||||||
case '.vtt':
|
|
||||||
contentType = 'text/vtt';
|
|
||||||
break;
|
|
||||||
case '.srt':
|
|
||||||
contentType = 'text/plain';
|
|
||||||
break;
|
|
||||||
case '.txt':
|
|
||||||
contentType = 'text/plain';
|
|
||||||
break;
|
|
||||||
case '.pdf':
|
|
||||||
contentType = 'application/pdf';
|
|
||||||
break;
|
|
||||||
case '.doc':
|
|
||||||
case '.docx':
|
|
||||||
contentType = 'application/msword';
|
|
||||||
break;
|
|
||||||
case '.ppt':
|
|
||||||
case '.pptx':
|
|
||||||
contentType = 'application/vnd.ms-powerpoint';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the file with appropriate headers
|
|
||||||
return new NextResponse(fileBuffer, {
|
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': contentType,
|
'Content-Type': contentType,
|
||||||
'Content-Disposition': `inline; filename="${path.basename(filePath)}"`,
|
'Content-Disposition': `inline; filename="${filename}"`,
|
||||||
'Cache-Control': 'public, max-age=86400' // Cache for 1 day
|
'Cache-Control': 'public, max-age=86400',
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error('Error serving file:', error);
|
return new NextResponse('Upstream unavailable', { status: 502 });
|
||||||
return new NextResponse('Internal Server Error', { status: 500 });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue