Enhance Docker deployment: structured logging, health checks, resource limits, entrypoint script

- Add structured logging config with JSON/text format support
- Create docker-entrypoint.sh with dependency health checks
- Enhance /health endpoint with DB and Redis connectivity checks
- Add resource limits (CPU/memory) for all services
- Add network aliases for service discovery
- Add container hostnames for better identification
- Reduce health check timeout from 20s to 5s
- Add build metadata labels to Dockerfile
- Use ENTRYPOINT for dependency checking before app startup
- Log rotation: 50m per file, 5 files max
This commit is contained in:
2026-08-25 03:14:30 +00:00
parent ad037de48d
commit 0c805b1442
7 changed files with 618 additions and 60 deletions
+62
View File
@@ -0,0 +1,62 @@
# MTG Online Backend - Docker Deployment
## Project Overview
MTG Online backend REST API with FastAPI, SQLAlchemy, and Alembic migrations. Provides user authentication, deck management, card collection tracking, game rooms, and protocol message handling for MTG Online client compatibility.
## Docker Deployment Status
### Current State
- **Docker Compose Stack**: Configured and tested
- **Port Mapping**: 8990:8000 (backend exposed on host port 8990)
- **Services**: 4 containers (backend, postgres, postgres_mtgdata, postgres_mirror, redis)
- **Logging**: JSON file logging with rotation (10MB max, 3 files)
### Recent Fixes Applied
1. **Database URL Fix**: Updated `.env` to use Docker service names instead of localhost
- `postgres_mtgdata` for mtgdata database
- `postgres` for mtgo_platform database
- `postgres_mirror` for mtgo_mirror database
2. **Docker Build**: Successfully built backend image
3. **Service Health**: All containers started and became healthy
### Known Issues
- **Backend Connection Error**: Backend container couldn't connect to PostgreSQL due to localhost vs Docker service name mismatch
- **Fix Applied**: Updated `.env` file with correct Docker service names
- **Status**: Fix applied but NOT yet re-tested after restart
### Next Steps Required
1. Restart Docker containers with updated `.env`
2. Verify backend can connect to all three PostgreSQL databases
3. Test health endpoint on port 8990
4. Test authentication and CRUD operations
5. Verify logging output
## Architecture
- **Primary Database**: mtgo_platform (user data, decks, cards)
- **MTG Data Database**: mtgdata (canonical card data)
- **Mirror Database**: mtgo_mirror (fast deckbuilding queries)
- **Cache**: Redis for session/cache management
- **Async Driver**: asyncpg for async SQLAlchemy support
## Commands
```bash
# Start stack
cd /home/wall-o/projects/mtgonline/backend
docker compose up -d
# View logs
docker compose logs -f backend
docker compose logs -f postgres
# Stop stack
docker compose down
# Rebuild after changes
docker compose up -d --build
```
## API Access
- **Health Check**: http://localhost:8990/health
- **API Docs**: http://localhost:8990/docs
- **Backend Port**: 8990 (mapped to container port 8000)
+25 -10
View File
@@ -3,33 +3,45 @@ FROM python:3.12-slim as builder
WORKDIR /app WORKDIR /app
# Install build dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \ gcc \
libpq-dev \ libpq-dev \
curl \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
RUN groupadd -r appuser && useradd -r -g appuser -d /app -s /sbin/nologin appuser # Install Python dependencies
COPY requirements.txt . COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt RUN pip install --no-cache-dir --user -r requirements.txt
# Application stage # Application stage
FROM python:3.12-slim FROM python:3.12-slim
LABEL org.label-schema.name="mtgonline-backend" \
org.label-schema.description="MTG Online Backend API" \
org.label-schema.version="0.2.0" \
org.label-schema.build-date="${BUILD_DATE}" \
org.label-schema.vcs-url="https://gitea.wallomation.com/wall-o/mtgonline"
WORKDIR /app WORKDIR /app
# Install runtime dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
libpq5 \ libpq5 \
curl \ curl \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Copy installed packages from builder
COPY --from=builder /root/.local /app/.local COPY --from=builder /root/.local /app/.local
ENV PATH=/app/.local/bin:$PATH ENV PATH=/app/.local/bin:$PATH
# Create non-root user
RUN groupadd -r appuser && useradd -r -g appuser -d /app -s /sbin/nologin appuser RUN groupadd -r appuser && useradd -r -g appuser -d /app -s /sbin/nologin appuser
RUN mkdir -p /app/data /app/uploads /app/logs /app/scripts /app/alembic/versions && chown -R appuser:appuser /app # Create necessary directories
RUN mkdir -p /app/data /app/uploads /app/logs /app/scripts /app/alembic/versions && \
chown -R appuser:appuser /app
# Copy application code # Copy application code
COPY --chown=appuser:appuser app/ ./app/ COPY --chown=appuser:appuser app/ ./app/
@@ -38,31 +50,34 @@ COPY --chown=appuser:appuser app/ ./app/
COPY --chown=appuser:appuser alembic.ini ./ COPY --chown=appuser:appuser alembic.ini ./
COPY --chown=appuser:appuser alembic/ ./alembic/ COPY --chown=appuser:appuser alembic/ ./alembic/
# Copy interaction pipeline scripts # Copy scripts (including entrypoint)
COPY --chown=appuser:appuser scripts/ ./scripts/ COPY --chown=appuser:appuser scripts/ ./scripts/
RUN chmod +x /app/scripts/*.py RUN chmod +x /app/scripts/*.py /app/scripts/docker-entrypoint.sh
# Copy environment example
COPY --chown=appuser:appuser .env.example ./.env.example COPY --chown=appuser:appuser .env.example ./.env.example
COPY --chown=appuser:appuser pyproject.toml ./ COPY --chown=appuser:appuser pyproject.toml ./
# Python environment variables for proper logging # Python environment variables
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONPATH=/app ENV PYTHONPATH=/app
# Logging configuration # Logging configuration
ENV MTG_LOG_LEVEL=INFO ENV MTG_LOG_LEVEL=INFO
ENV MTG_LOG_FORMAT=json ENV MTG_LOG_FORMAT=text
# Create logging directory with proper permissions # Create logging directory with proper permissions
RUN mkdir -p /app/logs && chown appuser:appuser /app/logs RUN mkdir -p /app/logs && chown appuser:appuser /app/logs
HEALTHCHECK --interval=30s --timeout=20s --start-period=5s --retries=3 \ # Health check
HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1 CMD curl -f http://localhost:8000/health || exit 1
USER appuser USER appuser
EXPOSE 8000 EXPOSE 8000
# Run migrations before starting the app # Use entrypoint script for dependency checking and migrations
CMD ["sh", "-c", "python -m alembic upgrade head && python -m uvicorn app.main:app --host 0.0.0.0 --port 8000"] ENTRYPOINT ["/app/scripts/docker-entrypoint.sh"]
CMD ["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--forwarded-allow-ips", "*"]
+97
View File
@@ -0,0 +1,97 @@
"""
Structured logging configuration for MTG Online backend.
Supports JSON and text log formats with container metadata.
Configured via MTG_LOG_LEVEL and MTG_LOG_FORMAT environment variables.
"""
import json
import logging
import os
import sys
from datetime import datetime, timezone
def _get_container_name() -> str:
"""Get container name from HOSTNAME env var or fallback."""
return os.environ.get("HOSTNAME", "unknown")
class ContainerFormatter(logging.Formatter):
"""Formatter that adds container metadata to log records.
Supports both text and JSON output formats.
"""
SERVICE_NAME = "mtgonline-backend"
def __init__(self, fmt_type: str = "text"):
super().__init__()
self.fmt_type = fmt_type
self.container_name = _get_container_name()
def format(self, record: logging.LogRecord) -> str:
record.service = self.SERVICE_NAME
record.container = self.container_name
if self.fmt_type == "json":
return self._format_json(record)
return self._format_text(record)
def _format_text(self, record: logging.LogRecord) -> str:
return (
f"{self.formatTime(record)} [{record.service}] "
f"{record.levelname} - {record.getMessage()}"
)
def _format_json(self, record: logging.LogRecord) -> str:
log_entry = {
"timestamp": datetime.fromtimestamp(
record.created, tz=timezone.utc
).isoformat(),
"level": record.levelname,
"service": record.service,
"container": record.container,
"logger": record.name,
"message": record.getMessage(),
}
if record.exc_info and record.exc_info[0] is not None:
log_entry["exception"] = self.formatException(record.exc_info)
return json.dumps(log_entry, default=str)
def configure_logging() -> None:
"""Configure application logging based on environment variables.
Reads:
MTG_LOG_LEVEL - Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
MTG_LOG_FORMAT - Output format ("text" or "json")
"""
log_level = os.environ.get("MTG_LOG_LEVEL", "INFO").upper()
log_format = os.environ.get("MTG_LOG_FORMAT", "text").lower()
level = getattr(logging, log_level, logging.INFO)
formatter = ContainerFormatter(fmt_type=log_format)
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(formatter)
root_logger = logging.getLogger()
root_logger.setLevel(level)
root_logger.handlers.clear()
root_logger.addHandler(handler)
# Suppress noisy loggers unless DEBUG
if level != logging.DEBUG:
logging.getLogger("sqlalchemy.engine").setLevel(logging.WARNING)
logging.getLogger("sqlalchemy.pool").setLevel(logging.WARNING)
logger = logging.getLogger(__name__)
logger.info(
f"Logging configured: level={log_level}, "
f"format={log_format}, container={_get_container_name()}"
)
# Auto-configure on import
configure_logging()
+37 -33
View File
@@ -21,32 +21,14 @@ from typing import AsyncGenerator
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy import text
from app.core.settings import get_settings from app.core.settings import get_settings
from app.core.database import engine, mtg_engine, async_session, mtg_async_session from app.core.database import engine, mtg_engine, async_session, mtg_async_session
from app.routers import auth, users, decks, rooms, admin, card_router, interactions, refresh, user_data, card_import from app.routers import auth, users, decks, rooms, admin, card_router, interactions, refresh, user_data, card_import
from app.routers.games import router as games_router from app.routers.games import router as games_router
from app.services.mtgjson_manager import MTGJSONManager from app.services.mtgjson_manager import MTGJSONManager
from app.core.logging_config import configure_logging
def setup_logging(debug: bool = False) -> None:
"""Configure application logging with verbose support."""
level = logging.DEBUG if debug else logging.INFO
logging.basicConfig(
level=level,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(),
]
)
if debug:
logging.getLogger('sqlalchemy.engine').setLevel(logging.WARNING)
logging.getLogger('sqlalchemy.pool').setLevel(logging.WARNING)
logger = logging.getLogger(__name__)
logger.info(f"Logging initialized at level {logging.getLevelName(level)}")
async def run_initial_download(): async def run_initial_download():
@@ -91,8 +73,8 @@ def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
"""Application lifespan events for startup and shutdown.""" """Application lifespan events for startup and shutdown."""
settings = get_settings() settings = get_settings()
# Startup # Configure logging
setup_logging(debug=settings.DEBUG) configure_logging()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logger.info(f"MTG Online Backend starting (v{settings.APP_VERSION})") logger.info(f"MTG Online Backend starting (v{settings.APP_VERSION})")
logger.info(f"Database: {settings.DATABASE_URL.split('@')[1] if '@' in settings.DATABASE_URL else 'configured'}") logger.info(f"Database: {settings.DATABASE_URL.split('@')[1] if '@' in settings.DATABASE_URL else 'configured'}")
@@ -146,24 +128,46 @@ app.include_router(card_import.router, prefix="/api/v1/card-import", tags=["Card
@app.get("/health", tags=["Health"]) @app.get("/health", tags=["Health"])
async def health_check(): async def health_check():
"""Health check endpoint with MTGJSON data status.""" """Health check endpoint with full service status."""
from app.services.mtgjson_manager import get_manager from app.services.mtgjson_manager import get_manager
# Get MTGJSON health status health_status = {"status": "healthy", "version": settings.APP_VERSION}
# Check MTGJSON status
try: try:
manager = get_manager() manager = get_manager()
mtg_status = await manager.get_health_status() mtg_status = await manager.get_health_status()
health_status["mtgjson"] = mtg_status
if mtg_status.get("status") != "healthy":
health_status["status"] = "degraded"
except Exception as e: except Exception as e:
mtg_status = { health_status["mtgjson"] = {"status": "unhealthy", "error": str(e)}
"status": "unhealthy", health_status["status"] = "degraded"
"error": str(e),
}
return { # Check primary database
"status": "healthy" if mtg_status.get("status") == "healthy" else "degraded", try:
"version": settings.APP_VERSION, from app.core.database import engine
"mtgjson": mtg_status, async with engine.connect() as conn:
} await conn.execute(text("SELECT 1"))
health_status["database"] = "healthy"
except Exception as e:
health_status["database"] = {"status": "unhealthy", "error": str(e)}
health_status["status"] = "unhealthy"
# Check Redis
try:
from app.core.redis_client import get_redis
redis_client = await get_redis()
if redis_client:
health_status["redis"] = "healthy"
else:
health_status["redis"] = {"status": "unhealthy", "error": "Connection failed"}
health_status["status"] = "unhealthy"
except Exception as e:
health_status["redis"] = {"status": "unhealthy", "error": str(e)}
health_status["status"] = "unhealthy"
return health_status
@app.get("/", tags=["Root"]) @app.get("/", tags=["Root"])
+104 -17
View File
@@ -3,6 +3,8 @@ services:
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
container_name: mtgonline_backend
hostname: mtgonline_backend
ports: ports:
- "8990:8000" - "8990:8000"
environment: environment:
@@ -16,7 +18,7 @@ services:
- MTG_INTERACTION_BATCH_SIZE=1000 - MTG_INTERACTION_BATCH_SIZE=1000
- MTG_INTERACTION_REVIEW_QUEUE=true - MTG_INTERACTION_REVIEW_QUEUE=true
- MTG_LOG_LEVEL=INFO - MTG_LOG_LEVEL=INFO
- MTG_LOG_FORMAT=json - MTG_LOG_FORMAT=text
- REDIS_URL=redis://redis:6379/0 - REDIS_URL=redis://redis:6379/0
- SECRET_KEY=change-this-secret-key-in-production - SECRET_KEY=change-this-secret-key-in-production
- JWT_SECRET_KEY=change-this-jwt-secret-key-in-production - JWT_SECRET_KEY=change-this-jwt-secret-key-in-production
@@ -38,42 +40,72 @@ services:
healthcheck: healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"] test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s interval: 30s
timeout: 20s timeout: 5s
retries: 3 retries: 3
start_period: 40s start_period: 40s
logging: logging:
driver: "json-file" driver: "json-file"
options: options:
max-size: "10m" max-size: "50m"
max-file: "3" max-file: "5"
tag: "{{.Name}}" tag: "{{.Name}}"
networks: networks:
- mtgonline_network mtgonline_network:
aliases:
- backend
- mtgonline-api
restart: unless-stopped
deploy:
resources:
limits:
memory: 1G
cpus: "1.0"
reservations:
memory: 512M
cpus: "0.5"
postgres_platform: postgres_platform:
image: postgres:16-alpine image: postgres:16-alpine
container_name: mtgonline_postgres_platform
hostname: postgres_platform
environment: environment:
- POSTGRES_USER=postgres - POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres - POSTGRES_PASSWORD=postgres
- POSTGRES_DB=mtgo_platform - POSTGRES_DB=mtgo_platform
volumes: volumes:
- postgres_platform_data:/var/lib/postgresql/data - postgres_platform_data:/var/lib/postgresql/data
- ./scripts/init_platform.sql:/docker-entrypoint-initdb.d/01-init.sql
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d mtgo_platform"] test: ["CMD-SHELL", "pg_isready -U postgres -d mtgo_platform"]
interval: 10s interval: 10s
timeout: 5s timeout: 5s
retries: 5 retries: 5
start_period: 10s
logging: logging:
driver: "json-file" driver: "json-file"
options: options:
max-size: "10m" max-size: "50m"
max-file: "3" max-file: "5"
tag: "{{.Name}}" tag: "{{.Name}}"
networks: networks:
- mtgonline_network mtgonline_network:
aliases:
- postgres-platform
- platform-db
restart: unless-stopped
deploy:
resources:
limits:
memory: 512M
cpus: "0.5"
reservations:
memory: 256M
cpus: "0.25"
postgres_mtgdata: postgres_mtgdata:
image: postgres:16-alpine image: postgres:16-alpine
container_name: mtgonline_postgres_mtgdata
hostname: postgres_mtgdata
environment: environment:
- POSTGRES_USER=postgres - POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres - POSTGRES_PASSWORD=postgres
@@ -85,17 +117,32 @@ services:
interval: 10s interval: 10s
timeout: 5s timeout: 5s
retries: 5 retries: 5
start_period: 10s
logging: logging:
driver: "json-file" driver: "json-file"
options: options:
max-size: "10m" max-size: "50m"
max-file: "3" max-file: "5"
tag: "{{.Name}}" tag: "{{.Name}}"
networks: networks:
- mtgonline_network mtgonline_network:
aliases:
- postgres-mtgdata
- mtgdata-db
restart: unless-stopped
deploy:
resources:
limits:
memory: 512M
cpus: "0.5"
reservations:
memory: 256M
cpus: "0.25"
postgres_mirror: postgres_mirror:
image: postgres:16-alpine image: postgres:16-alpine
container_name: mtgonline_postgres_mirror
hostname: postgres_mirror
environment: environment:
- POSTGRES_USER=postgres - POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres - POSTGRES_PASSWORD=postgres
@@ -107,39 +154,79 @@ services:
interval: 10s interval: 10s
timeout: 5s timeout: 5s
retries: 5 retries: 5
start_period: 10s
logging: logging:
driver: "json-file" driver: "json-file"
options: options:
max-size: "10m" max-size: "50m"
max-file: "3" max-file: "5"
tag: "{{.Name}}" tag: "{{.Name}}"
networks: networks:
- mtgonline_network mtgonline_network:
aliases:
- postgres-mirror
- mirror-db
restart: unless-stopped
deploy:
resources:
limits:
memory: 512M
cpus: "0.5"
reservations:
memory: 256M
cpus: "0.25"
redis: redis:
image: redis:7-alpine image: redis:7-alpine
container_name: mtgonline_redis
hostname: redis
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
healthcheck: healthcheck:
test: ["CMD", "redis-cli", "ping"] test: ["CMD", "redis-cli", "ping"]
interval: 10s interval: 10s
timeout: 5s timeout: 5s
retries: 5 retries: 5
start_period: 5s
logging: logging:
driver: "json-file" driver: "json-file"
options: options:
max-size: "10m" max-size: "50m"
max-file: "3" max-file: "5"
tag: "{{.Name}}" tag: "{{.Name}}"
networks: networks:
- mtgonline_network mtgonline_network:
aliases:
- redis
- cache
restart: unless-stopped
deploy:
resources:
limits:
memory: 512M
cpus: "0.5"
reservations:
memory: 256M
cpus: "0.25"
volumes: volumes:
postgres_platform_data: postgres_platform_data:
driver: local
postgres_mtgdata_data: postgres_mtgdata_data:
driver: local
postgres_mirror_data: postgres_mirror_data:
driver: local
mtg_data: mtg_data:
driver: local
mtg_uploads: mtg_uploads:
driver: local
mtg_logs: mtg_logs:
driver: local
redis_data:
driver: local
networks: networks:
mtgonline_network: mtgonline_network:
driver: bridge driver: bridge
name: mtgonline_network
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
set -e
echo "============================================"
echo " MTG Online Backend - Starting"
echo " $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
echo "============================================"
# Wait for PostgreSQL to be ready
echo "[entrypoint] Waiting for PostgreSQL databases..."
for db in mtgo_platform mtgdata mtgo_mirror; do
host="postgres_${db}"
echo " Checking ${host}..."
until python -c "
import asyncio, asyncpg
async def check():
try:
conn = await asyncpg.connect(host='${host}', port=5432, user='postgres', password='postgres', database='${db}')
await conn.close()
return True
except Exception:
return False
asyncio.run(check())
" 2>/dev/null; do
echo " ${host} not ready, waiting 2s..."
sleep 2
done
echo " ${host} is ready!"
done
# Wait for Redis to be ready
echo "[entrypoint] Waiting for Redis..."
until python -c "
import asyncio, redis.asyncio as aioredis
async def check():
try:
r = aioredis.from_url('redis://redis:6379/0')
await r.ping()
await r.close()
return True
except Exception:
return False
asyncio.run(check())
" 2>/dev/null; do
echo " Redis not ready, waiting 2s..."
sleep 2
done
echo " Redis is ready!"
# Run database migrations
echo "[entrypoint] Running Alembic migrations..."
python -m alembic upgrade head
echo "[entrypoint] Migrations complete."
# Start the application
echo "[entrypoint] Starting uvicorn..."
exec python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --forwarded-allow-ips '*' --log-level info
+236
View File
@@ -0,0 +1,236 @@
#!/bin/bash
# MTG Online Backend - Docker Deployment Helper Scripts
# ============================================================
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Project root directory
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} MTG Online Backend - Docker Deployment${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
# Function to display usage
usage() {
echo "Usage: $0 <command>"
echo ""
echo "Commands:"
echo " start - Start all services"
echo " stop - Stop all services"
echo " restart - Restart all services"
echo " logs - View logs (follow mode)"
echo " logs-app - View application logs only"
echo " logs-db - View database logs only"
echo " status - Show service status"
echo " health - Check health of all services"
echo " exec-app - Execute command in backend container"
echo " exec-db - Execute command in database container"
echo " migrate - Run database migrations"
echo " cleanup - Stop and remove all containers and volumes"
echo " rebuild - Rebuild and start all services"
echo " help - Show this help message"
echo ""
exit 0
}
# Function to check if docker is running
check_docker() {
if ! docker info > /dev/null 2>&1; then
echo -e "${RED}Error: Docker is not running${NC}"
exit 1
fi
}
# Function to check if docker-compose is available
check_compose() {
if ! command -v docker-compose &> /dev/null; then
echo -e "${RED}Error: docker-compose is not installed${NC}"
exit 1
fi
}
# Function to start services
start_services() {
echo -e "${GREEN}Starting MTG Online Backend services...${NC}"
cd "$PROJECT_ROOT/backend"
docker-compose up -d
echo -e "${GREEN}✓ Services started successfully${NC}"
echo ""
echo -e "${BLUE}Access the application at: http://localhost:8990${NC}"
echo -e "${BLUE}View logs with: $0 logs${NC}"
}
# Function to stop services
stop_services() {
echo -e "${YELLOW}Stopping MTG Online Backend services...${NC}"
cd "$PROJECT_ROOT/backend"
docker-compose down
echo -e "${GREEN}✓ Services stopped${NC}"
}
# Function to restart services
restart_services() {
echo -e "${YELLOW}Restarting MTG Online Backend services...${NC}"
cd "$PROJECT_ROOT/backend"
docker-compose restart
echo -e "${GREEN}✓ Services restarted${NC}"
}
# Function to view logs
view_logs() {
cd "$PROJECT_ROOT/backend"
docker-compose logs -f --tail=100
}
# Function to view application logs only
view_app_logs() {
cd "$PROJECT_ROOT/backend"
docker-compose logs -f --tail=100 backend
}
# Function to view database logs only
view_db_logs() {
cd "$PROJECT_ROOT/backend"
docker-compose logs -f --tail=100 postgres_platform postgres_mtgdata postgres_mirror
}
# Function to show status
show_status() {
cd "$PROJECT_ROOT/backend"
echo -e "${BLUE}Service Status:${NC}"
docker-compose ps
}
# Function to check health
check_health() {
cd "$PROJECT_ROOT/backend"
echo -e "${BLUE}Checking service health...${NC}"
docker-compose exec -T backend curl -s http://localhost:8000/health | python3 -m json.tool
echo ""
echo -e "${BLUE}Database Health:${NC}"
docker-compose exec -T postgres_platform pg_isready -U postgres
docker-compose exec -T postgres_mtgdata pg_isready -U postgres
docker-compose exec -T postgres_mirror pg_isready -U postgres
echo ""
echo -e "${BLUE}Redis Health:${NC}"
docker-compose exec -T redis redis-cli ping
}
# Function to execute command in backend container
exec_app() {
cd "$PROJECT_ROOT/backend"
docker-compose exec backend "$@"
}
# Function to execute command in database container
exec_db() {
local db_name=$1
shift
cd "$PROJECT_ROOT/backend"
docker-compose exec "$db_name" psql -U postgres "$@"
}
# Function to run migrations
run_migrations() {
echo -e "${YELLOW}Running database migrations...${NC}"
cd "$PROJECT_ROOT/backend"
docker-compose exec backend python -m alembic upgrade head
echo -e "${GREEN}✓ Migrations completed${NC}"
}
# Function to cleanup
cleanup() {
echo -e "${RED}WARNING: This will stop and remove all containers and volumes!${NC}"
read -p "Are you sure? (y/N) " -n 1 -r
echo ""
if [[ $REPLY =~ ^[Yy]$ ]]; then
cd "$PROJECT_ROOT/backend"
docker-compose down -v
echo -e "${GREEN}✓ All containers and volumes removed${NC}"
fi
}
# Function to rebuild
rebuild() {
echo -e "${YELLOW}Rebuilding and starting all services...${NC}"
cd "$PROJECT_ROOT/backend"
docker-compose down
docker-compose build --no-cache
docker-compose up -d
echo -e "${GREEN}✓ Services rebuilt and started${NC}"
echo ""
echo -e "${BLUE}Access the application at: http://localhost:8990${NC}"
}
# Main command handler
case "${1:-help}" in
start)
check_docker
start_services
;;
stop)
check_docker
stop_services
;;
restart)
check_docker
restart_services
;;
logs)
check_docker
view_logs
;;
logs-app)
check_docker
view_app_logs
;;
logs-db)
check_docker
view_db_logs
;;
status)
check_docker
show_status
;;
health)
check_docker
check_health
;;
exec-app)
check_docker
shift
exec_app "$@"
;;
exec-db)
check_docker
exec_db "$@"
;;
migrate)
check_docker
run_migrations
;;
cleanup)
check_docker
cleanup
;;
rebuild)
check_docker
rebuild
;;
help|--help|-h)
usage
;;
*)
echo -e "${RED}Unknown command: $1${NC}"
usage
;;
esac