feat: add production Dockerfile and docker-compose configuration for backend setup

This commit is contained in:
Sam Chau
2025-02-05 13:51:47 +10:30
parent 33a24b764d
commit b335ca73ed
5 changed files with 94 additions and 35 deletions

6
.gitignore vendored
View File

@@ -8,8 +8,12 @@ __pycache__/
# Environment variables
.env
.env.prod
.env.1
.env.2
# OS files
.DS_Store
.DS_Store
# build files
backend/app/static/

17
backend/Dockerfile.prod Normal file
View File

@@ -0,0 +1,17 @@
# backend/Dockerfile.prod
# Build frontend
FROM node:18 AS frontend-builder
WORKDIR /frontend
COPY frontend/package*.json ./
RUN npm install
COPY frontend/ ./
RUN npm run build
# Backend
FROM python:3.9
WORKDIR /app
COPY backend/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY backend/ .
COPY --from=frontend-builder /frontend/dist ./app/static
CMD ["python", "-m", "app.main"]

View File

@@ -23,9 +23,19 @@ def create_app():
logger = setup_logging()
logger.info("Creating Flask application")
app = Flask(__name__)
app = Flask(__name__, static_folder='static')
CORS(app, resources={r"/*": {"origins": "*"}})
# Serve static files
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def serve_static(path):
if path.startswith('api/'):
return # Let API routes handle these
if path and os.path.exists(os.path.join(app.static_folder, path)):
return send_from_directory(app.static_folder, path)
return send_from_directory(app.static_folder, 'index.html')
# Initialize directories and database
logger.info("Ensuring required directories exist")
config.ensure_directories()
@@ -58,7 +68,7 @@ def create_app():
app.register_blueprint(logs_bp, url_prefix='/api/logs')
app.register_blueprint(git_bp, url_prefix='/api/git')
app.register_blueprint(data_bp, url_prefix='/api/data')
app.register_blueprint(importarr_bp, url_prefix='/api/importarr')
app.register_blueprint(importarr_bp, url_prefix='/api/import')
app.register_blueprint(arr_bp, url_prefix='/api/arr')
app.register_blueprint(tasks_bp, url_prefix='/api/tasks')
@@ -67,7 +77,7 @@ def create_app():
init_middleware(app)
# Add settings route
@app.route('/settings', methods=['GET'])
@app.route('/api/settings', methods=['GET'])
def handle_settings():
settings = get_settings()
return jsonify(settings), 200

View File

@@ -1,7 +1,6 @@
# backend/app/middleware.py
from functools import wraps
from flask import request, session, jsonify, current_app
from flask import request, session, jsonify, send_from_directory
from .db import get_db
import logging
@@ -13,41 +12,49 @@ def init_middleware(app):
@app.before_request
def authenticate_request():
# Skip authentication for auth blueprint routes
if request.blueprint == 'auth':
return
# Skip authentication for OPTIONS requests (CORS preflight)
if request.method == 'OPTIONS':
return
# List of paths that don't require authentication
PUBLIC_PATHS = ['/auth/setup', '/auth/authenticate']
if request.path in PUBLIC_PATHS:
# Always allow auth endpoints
if request.path.startswith('/api/auth/'):
return
# Check session authentication (for web users)
if session.get('authenticated'):
db = get_db()
user = db.execute('SELECT session_id FROM auth').fetchone()
if user and session.get('session_id') == user['session_id']:
return
# Allow static assets needed for auth pages
if request.path.startswith(
('/assets/',
'/static/')) or request.path in ['/', '/regex.svg', '/clone.svg']:
return
# Check API key authentication (for API users)
api_key = request.headers.get('X-Api-Key')
if api_key:
db = get_db()
try:
user = db.execute('SELECT 1 FROM auth WHERE api_key = ?',
(api_key, )).fetchone()
if user:
# For API routes, require auth
if request.path.startswith('/api/'):
# Check session authentication (for web users)
if session.get('authenticated'):
db = get_db()
user = db.execute('SELECT session_id FROM auth').fetchone()
if user and session.get('session_id') == user['session_id']:
return
logger.warning(f'Invalid API key attempt: {api_key[:10]}...')
except Exception as e:
logger.error(f'Database error during API key check: {str(e)}')
return jsonify({'error': 'Internal server error'}), 500
# If no valid authentication is found, return 401
logger.warning(f'Unauthorized access attempt to {request.path}')
return jsonify({'error': 'Unauthorized'}), 401
# Check API key authentication (for API users)
api_key = request.headers.get('X-Api-Key')
if api_key:
db = get_db()
try:
user = db.execute('SELECT 1 FROM auth WHERE api_key = ?',
(api_key, )).fetchone()
if user:
return
logger.warning(
f'Invalid API key attempt: {api_key[:10]}...')
except Exception as e:
logger.error(
f'Database error during API key check: {str(e)}')
return jsonify({'error': 'Internal server error'}), 500
# If no valid authentication is found, return 401
logger.warning(f'Unauthorized access attempt to {request.path}')
return jsonify({'error': 'Unauthorized'}), 401
# For all other routes (frontend routes), serve index.html
# This lets React handle auth and routing
return send_from_directory(app.static_folder, 'index.html')

21
docker-compose.prod.yml Normal file
View File

@@ -0,0 +1,21 @@
# docker-compose.prod.yml
version: '3.8'
services:
backend:
build:
context: .
dockerfile: backend/Dockerfile.prod
ports:
- '5000:5000'
volumes:
- profilarr_data:/config
environment:
- FLASK_ENV=production
- TZ=Australia/Adelaide
env_file:
- .env.prod
restart: always
volumes:
profilarr_data:
name: profilarr_data