Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.git
.gitignore
node_modules
build
__pycache__
*.py[cod]
.pytest_cache
.mypy_cache
.coverage
htmlcov
.env
.env.*
!.env.example
logs
*.log
.DS_Store
32 changes: 20 additions & 12 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,29 +1,38 @@
# Database
DATABASE_URL=postgresql+asyncpg://appuser:password@localhost:5432/gateway_hub
# Database (Compose services use the internal postgres hostname and a sync SQLAlchemy URL)
DATABASE_URL=postgresql://appuser:password123@postgres:5432/gateway_hub
DB_NAME=gateway_hub
DB_USER=appuser
DB_PASSWORD=your_secure_password_here
DB_PASSWORD=password123
# DB_PORT is only needed if you add a local override that publishes Postgres.
DB_PORT=5432

# Redis
REDIS_URL=redis://localhost:6379
# Redis (Compose services use the internal redis hostname)
REDIS_URL=redis://redis:6379
# REDIS_PORT is only needed if you add a local override that publishes Redis.
REDIS_PORT=6379

# JWT & Security
SECRET_KEY=your-super-secret-key-change-in-production
SECRET_KEY=dev-compose-secret-key-change-me
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=30
REFRESH_TOKEN_EXPIRE_DAYS=7

# Short Links
SHORT_CODE_LENGTH=6
SHORT_CODE_ALPHABET=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
SHORT_URL_BASE=https://yourdomain.com/r
SHORT_URL_BASE=http://localhost:8000/r

# Application
APP_NAME=Gateway Hub
DEBUG=false
ENVIRONMENT=production
ALLOWED_ORIGINS=http://localhost:3000,https://app.example.com
ENVIRONMENT=development
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8000,http://localhost:8080

# Local demo user
LOCAL_DEMO_USER_ENABLED=true
LOCAL_DEMO_USERNAME=user
LOCAL_DEMO_PASSWORD=password123
LOCAL_DEMO_EMAIL=user@example.com

# Rate Limiting
RATE_LIMIT_ENABLED=true
Expand All @@ -47,8 +56,7 @@ REACT_APP_API_URL=http://localhost:8000
REACT_APP_ENABLE_ANALYTICS=true
REACT_APP_ENABLE_CUSTOM_DOMAINS=false

# Ports
# Published web ports
BACKEND_PORT=8000
FRONTEND_PORT=3000
DB_PORT=5432
REDIS_PORT=6379
NGINX_PORT=8080
8 changes: 6 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ FROM python:3.11-slim

WORKDIR /app

ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1

# Install system dependencies
RUN apt-get update && apt-get install -y \
postgresql-client \
Expand All @@ -22,11 +25,12 @@ COPY scripts/ ./scripts/
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser

EXPOSE 8000

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import httpx; httpx.get('http://localhost:8000/health')" || exit 1

# Run migrations as an explicit deployment step before starting this command:
# /app/scripts/migrate.sh
# Compose runs /app/scripts/migrate.sh in the migrate job before backend startup.
# Run application
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
26 changes: 26 additions & 0 deletions Dockerfile.frontend
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Build the React frontend and serve the static bundle with Nginx.
FROM node:20-alpine AS build

WORKDIR /app

COPY package.json ./
RUN npm install --no-audit --no-fund

COPY public/ ./public/
COPY src/ ./src/
COPY tsconfig.json ./

ARG REACT_APP_API_URL=http://localhost:8000
ENV REACT_APP_API_URL=${REACT_APP_API_URL}

RUN npm run build

FROM nginx:1.27-alpine AS runtime

COPY --from=build /app/build /usr/share/nginx/html
COPY docker/frontend.conf /etc/nginx/conf.d/default.conf

HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD wget -qO- http://localhost/health >/dev/null || exit 1

CMD ["nginx", "-g", "daemon off;"]
150 changes: 54 additions & 96 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -569,125 +569,83 @@ sudo apt install certbot python3-certbot-nginx
sudo certbot certonly --nginx -d api.yourdomain.com
```

### 8.4 Docker Setup (Optional but Recommended)
### 8.4 Docker Compose Full-Stack Setup

```dockerfile
# Dockerfile
FROM python:3.11-slim
Docker Compose can run the complete local stack: FastAPI backend, React frontend, PostgreSQL, Redis, and an optional Nginx reverse proxy.

WORKDIR /app
**Services included:**

# Install system dependencies
RUN apt-get update && apt-get install -y postgresql-client && rm -rf /var/lib/apt/lists/*
- `postgres` — PostgreSQL 15 database used by the backend.
- `redis` — Redis 7 cache/rate-limit store used by the backend.
- `migrate` — one-shot Alembic migration job that runs before the backend starts.
- `backend` — FastAPI API served by Uvicorn on `http://localhost:8000` by default.
- `frontend` — production React static build served by Nginx on `http://localhost:3000` by default.
- `nginx` — optional reverse proxy profile on `http://localhost:8080` that routes `/api/` and `/r/` to the backend and all other requests to the frontend.

# Copy requirements
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
**Prepare environment variables:**

# Copy application
COPY app/ ./app/
COPY migrations/ ./migrations/
COPY alembic.ini .
COPY scripts/ ./scripts/
```bash
cp .env.example .env
```

# Create non-root user
RUN useradd -m appuser && chown -R appuser:appuser /app
USER appuser
The backend uses synchronous SQLAlchemy/psycopg2, so Compose uses a sync PostgreSQL URL:

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import httpx; httpx.get('http://localhost:8000/health')"
```env
DATABASE_URL=postgresql://appuser:password123@postgres:5432/gateway_hub
REDIS_URL=redis://redis:6379
REACT_APP_API_URL=http://localhost:8000
```

**Start the default local stack:**

# Run migrations as an explicit deployment step before starting this command:
# /app/scripts/migrate.sh
# Run application
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
```bash
docker compose up --build
```

```yaml
# docker-compose.yml
version: '3.9'
The `migrate` job runs `alembic upgrade head` automatically after PostgreSQL is healthy. The backend waits for that job to complete successfully before it starts, so a normal `docker compose up` creates a coherent application stack without a separate migration command.

**Open the application:**

- Frontend: `http://localhost:3000`
- Backend health check: `http://localhost:8000/health`
- API base path: `http://localhost:8000/api/v1`

**Run with the optional Nginx reverse proxy:**

```bash
docker compose --profile proxy up --build
```

When the `proxy` profile is enabled, open `http://localhost:8080`. Nginx serves the frontend and proxies API calls to the backend from the same origin.

**Database and Redis ports:**

PostgreSQL and Redis are intentionally not published to the host by default. They are reachable by other Compose services on the internal Docker network as `postgres:5432` and `redis:6379`, which avoids exposing database/cache ports in production-like runs. If you need host access for a local database client, add a temporary `docker-compose.override.yml` such as:

```yaml
services:
db:
image: postgres:15-alpine
container_name: gateway-hub-db
environment:
POSTGRES_DB: gateway_hub
POSTGRES_USER: appuser
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
postgres:
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser"]
interval: 10s
timeout: 5s
retries: 5

redis:
image: redis:7-alpine
container_name: gateway-hub-redis
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5

migrate:
build: .
container_name: gateway-hub-migrate
environment:
DATABASE_URL: postgresql://appuser:${DB_PASSWORD}@db:5432/gateway_hub
depends_on:
db:
condition: service_healthy
command: ["/app/scripts/migrate.sh"]
profiles:
- migrations

backend:
build: .
container_name: gateway-hub-backend
environment:
DATABASE_URL: postgresql://appuser:${DB_PASSWORD}@db:5432/gateway_hub
REDIS_URL: redis://redis:6379
SECRET_KEY: ${SECRET_KEY}
DEBUG: "false"
ports:
- "8000:8000"
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
volumes:
- ./app:/app/app
- ./logs:/app/logs

volumes:
postgres_data:

networks:
default:
name: gateway-hub-network
```

**Run with Docker:**
**Useful commands:**

```bash
# Start dependencies first
docker-compose up -d db redis
# Re-run migrations manually if needed
docker compose run --rm migrate

# Follow backend logs
docker compose logs -f backend

# Apply schema migrations explicitly
docker-compose run --rm migrate
# Stop and remove containers, keeping named volumes
docker compose down

# Start the API after migrations have completed
docker-compose up -d backend
docker-compose logs -f backend
# Stop and remove containers plus database/cache volumes
docker compose down -v
```

### 8.5 CI/CD Pipeline (GitHub Actions)
Expand Down
Loading
Loading