Add Railway deployment configuration
- Add Dockerfiles for backend (FastAPI) and frontend (nginx) - Add railway.toml configs with health checks - Add .dockerignore files for optimized builds - Update config.py to auto-convert Railway DATABASE_URL format - Add FRONTEND_URL env var for production CORS Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
20
backend/.dockerignore
Normal file
20
backend/.dockerignore
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
.Python
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
.venv
|
||||||
|
venv/
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.pytest_cache
|
||||||
|
.mypy_cache
|
||||||
|
*.egg-info
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
|
.tox
|
||||||
|
*.log
|
||||||
22
backend/Dockerfile
Normal file
22
backend/Dockerfile
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
gcc \
|
||||||
|
libpq-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install Python dependencies
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copy application code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Expose port (Railway uses PORT env variable)
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# Run the application - use shell form to expand $PORT
|
||||||
|
CMD uvicorn app.main:app --host 0.0.0.0 --port ${PORT:-8000}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
from pydantic_settings import BaseSettings
|
from pydantic_settings import BaseSettings
|
||||||
|
from pydantic import field_validator
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
@@ -8,10 +9,26 @@ class Settings(BaseSettings):
|
|||||||
DEBUG: bool = False
|
DEBUG: bool = False
|
||||||
API_V1_PREFIX: str = "/api/v1"
|
API_V1_PREFIX: str = "/api/v1"
|
||||||
|
|
||||||
# Database
|
# Database - Railway provides DATABASE_URL, we convert it for asyncpg
|
||||||
DATABASE_URL: str = "postgresql+asyncpg://postgres:postgres@localhost:5432/patherly"
|
DATABASE_URL: str = "postgresql+asyncpg://postgres:postgres@localhost:5432/patherly"
|
||||||
DATABASE_URL_SYNC: str = "postgresql://postgres:postgres@localhost:5432/patherly"
|
DATABASE_URL_SYNC: str = "postgresql://postgres:postgres@localhost:5432/patherly"
|
||||||
|
|
||||||
|
@field_validator("DATABASE_URL", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def convert_database_url(cls, v: str) -> str:
|
||||||
|
"""Convert standard postgres URL to asyncpg format."""
|
||||||
|
if v.startswith("postgresql://"):
|
||||||
|
return v.replace("postgresql://", "postgresql+asyncpg://", 1)
|
||||||
|
return v
|
||||||
|
|
||||||
|
@field_validator("DATABASE_URL_SYNC", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def ensure_sync_url(cls, v: str) -> str:
|
||||||
|
"""Ensure sync URL uses standard postgresql prefix."""
|
||||||
|
if v.startswith("postgresql+asyncpg://"):
|
||||||
|
return v.replace("postgresql+asyncpg://", "postgresql://", 1)
|
||||||
|
return v
|
||||||
|
|
||||||
# JWT Settings
|
# JWT Settings
|
||||||
SECRET_KEY: str = "your-secret-key-change-in-production-use-openssl-rand-hex-32"
|
SECRET_KEY: str = "your-secret-key-change-in-production-use-openssl-rand-hex-32"
|
||||||
ALGORITHM: str = "HS256"
|
ALGORITHM: str = "HS256"
|
||||||
@@ -21,8 +38,17 @@ class Settings(BaseSettings):
|
|||||||
# Security
|
# Security
|
||||||
BCRYPT_ROUNDS: int = 12
|
BCRYPT_ROUNDS: int = 12
|
||||||
|
|
||||||
# CORS
|
# CORS - set FRONTEND_URL in production (e.g., https://patherly.up.railway.app)
|
||||||
CORS_ORIGINS: list[str] = ["http://localhost:3000", "http://localhost:5173", "http://localhost:5174"]
|
CORS_ORIGINS: list[str] = ["http://localhost:3000", "http://localhost:5173", "http://localhost:5174"]
|
||||||
|
FRONTEND_URL: Optional[str] = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def allowed_origins(self) -> list[str]:
|
||||||
|
"""Get all allowed CORS origins including FRONTEND_URL if set."""
|
||||||
|
origins = self.CORS_ORIGINS.copy()
|
||||||
|
if self.FRONTEND_URL and self.FRONTEND_URL not in origins:
|
||||||
|
origins.append(self.FRONTEND_URL)
|
||||||
|
return origins
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
env_file = ".env"
|
env_file = ".env"
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ app.add_middleware(RequestLoggingMiddleware)
|
|||||||
# Configure CORS
|
# Configure CORS
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=settings.CORS_ORIGINS,
|
allow_origins=settings.allowed_origins,
|
||||||
allow_credentials=True,
|
allow_credentials=True,
|
||||||
allow_methods=["*"],
|
allow_methods=["*"],
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
|
|||||||
9
backend/railway.toml
Normal file
9
backend/railway.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[build]
|
||||||
|
builder = "dockerfile"
|
||||||
|
dockerfilePath = "Dockerfile"
|
||||||
|
|
||||||
|
[deploy]
|
||||||
|
healthcheckPath = "/health"
|
||||||
|
healthcheckTimeout = 100
|
||||||
|
restartPolicyType = "on_failure"
|
||||||
|
restartPolicyMaxRetries = 3
|
||||||
10
frontend/.dockerignore
Normal file
10
frontend/.dockerignore
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
*.log
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
.vscode
|
||||||
|
coverage
|
||||||
|
.eslintcache
|
||||||
34
frontend/Dockerfile
Normal file
34
frontend/Dockerfile
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Build stage
|
||||||
|
FROM node:20-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build argument for API URL (set at build time)
|
||||||
|
ARG VITE_API_URL
|
||||||
|
ENV VITE_API_URL=$VITE_API_URL
|
||||||
|
|
||||||
|
# Build the application
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Production stage
|
||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
# Copy custom nginx config
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
# Copy built files from builder stage
|
||||||
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
21
frontend/nginx.conf
Normal file
21
frontend/nginx.conf
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# Gzip compression
|
||||||
|
gzip on;
|
||||||
|
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
||||||
|
|
||||||
|
# Handle SPA routing - serve index.html for all routes
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cache static assets
|
||||||
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
}
|
||||||
9
frontend/railway.toml
Normal file
9
frontend/railway.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[build]
|
||||||
|
builder = "dockerfile"
|
||||||
|
dockerfilePath = "Dockerfile"
|
||||||
|
|
||||||
|
[deploy]
|
||||||
|
healthcheckPath = "/"
|
||||||
|
healthcheckTimeout = 100
|
||||||
|
restartPolicyType = "on_failure"
|
||||||
|
restartPolicyMaxRetries = 3
|
||||||
Reference in New Issue
Block a user