From 25ea6b2fbcc3442623b451ee2f19db029c2cb2fa Mon Sep 17 00:00:00 2001 From: Rodrigo Agundez Date: Tue, 25 Nov 2025 23:19:09 +0800 Subject: [PATCH] Remove GUNICORN and NGINX support and optimized for containirized applications running with uvicorn only --- .../.env.example => .env.example | 0 .gitignore | 11 -- .../Dockerfile => Dockerfile | 2 - README.md | 140 ++++---------- default.conf | 32 ---- .../docker-compose.yml => docker-compose.yml | 10 - .../configuration/environment-variables.md | 1 - pyproject.toml | 1 - .../.env.example | 67 ------- .../Dockerfile | 27 --- .../docker-compose.yml | 111 ----------- scripts/production_with_nginx/.env.example | 67 ------- scripts/production_with_nginx/Dockerfile | 27 --- .../production_with_nginx/docker-compose.yml | 110 ----------- setup.py | 176 ------------------ uv.lock | 14 -- 16 files changed, 37 insertions(+), 759 deletions(-) rename scripts/local_with_uvicorn/.env.example => .env.example (100%) rename scripts/local_with_uvicorn/Dockerfile => Dockerfile (89%) delete mode 100644 default.conf rename scripts/local_with_uvicorn/docker-compose.yml => docker-compose.yml (88%) delete mode 100644 scripts/gunicorn_managing_uvicorn_workers/.env.example delete mode 100644 scripts/gunicorn_managing_uvicorn_workers/Dockerfile delete mode 100644 scripts/gunicorn_managing_uvicorn_workers/docker-compose.yml delete mode 100644 scripts/production_with_nginx/.env.example delete mode 100644 scripts/production_with_nginx/Dockerfile delete mode 100644 scripts/production_with_nginx/docker-compose.yml delete mode 100755 setup.py diff --git a/scripts/local_with_uvicorn/.env.example b/.env.example similarity index 100% rename from scripts/local_with_uvicorn/.env.example rename to .env.example diff --git a/.gitignore b/.gitignore index 9dec4471..ab9ad704 100644 --- a/.gitignore +++ b/.gitignore @@ -135,14 +135,3 @@ cython_debug/ .idea .vscode/ - -# Config files: -src/.env - -# Ignore root files: -/Dockerfile -/docker-compose.yml - -# Don't ignore files inside of script folder: -!scripts/* - diff --git a/scripts/local_with_uvicorn/Dockerfile b/Dockerfile similarity index 89% rename from scripts/local_with_uvicorn/Dockerfile rename to Dockerfile index 14571c9f..6395d8be 100644 --- a/scripts/local_with_uvicorn/Dockerfile +++ b/Dockerfile @@ -42,6 +42,4 @@ USER app # Set the working directory WORKDIR /code -# -------- replace with comment to run with gunicorn -------- CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload", "--reload-include", ".env"] -# CMD ["gunicorn", "app.main:app", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "-b", "0.0.0.0:8000"] diff --git a/README.md b/README.md index b9b2ef1e..292a9526 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +# ALMOST ALL DOCUMENTATION WILL HAVE TO BE UPDATED TO REFLECT THE CHANGES AND THE SIMPLIFICATION OF BEING CONTAINER FIRST CONFIGURATION, BETTER TO DO THIS AFTER THE CODE IS DONE AND AGREED UPON. I STARTED BUT I DO NOT WANT TO REPEAT OR REDO SECTIONS SO BETTER TO WAIT ON THE FEEDBACK ON CODE CHANGES I THINK. +

Benav Labs FastAPI boilerplate

Batteries-included FastAPI starter with production-ready defaults, optional modules, and clear docs. @@ -30,143 +32,75 @@ ## Features -* āš”ļø Fully async FastAPI + SQLAlchemy 2.0 -* 🧱 Pydantic v2 models & validation -* šŸ” JWT auth (access + refresh), cookies for refresh -* šŸ‘® Rate limiter + tiers (free/pro/etc.) -* 🧰 FastCRUD for efficient CRUD & pagination -* šŸ§‘ā€šŸ’¼ **CRUDAdmin**: minimal admin panel (optional) -* 🚦 ARQ background jobs (Redis) -* 🧊 Redis caching (server + client-side headers) -* 🌐 Configurable CORS middleware for frontend integration -* 🐳 One-command Docker Compose -* šŸš€ NGINX & Gunicorn recipes for prod +- āš”ļø Fully async FastAPI + SQLAlchemy 2.0 +- 🧱 Pydantic v2 models & validation +- šŸ” JWT auth (access + refresh), cookies for refresh +- šŸ‘® Rate limiter + tiers (free/pro/etc.) +- 🧰 FastCRUD for efficient CRUD & pagination +- šŸ§‘ā€šŸ’¼ **CRUDAdmin**: minimal admin panel (optional) +- 🚦 ARQ background jobs (Redis) +- 🧊 Redis caching (server + client-side headers) +- 🌐 Configurable CORS middleware for frontend integration +- 🐳 One-command Docker Compose +- šŸš€ Optimized for containarized deployments ## Why and When to use it **Perfect if you want:** -* A pragmatic starter with auth, CRUD, jobs, caching and rate-limits -* **Sensible defaults** with the freedom to opt-out of modules -* **Docs over boilerplate** in README - depth lives in the site +- A pragmatic starter with auth, CRUD, jobs, caching and rate-limits +- **Sensible defaults** with the freedom to opt-out of modules +- **Docs over boilerplate** in README - depth lives in the site > **Not a fit** if you need a monorepo microservices scaffold - [see the docs](https://benavlabs.github.io/FastAPI-boilerplate/user-guide/project-structure/) for pointers. **What you get:** -* **App**: FastAPI app factory, [env-aware docs](https://benavlabs.github.io/FastAPI-boilerplate/user-guide/development/) exposure -* **Auth**: [JWT access/refresh](https://benavlabs.github.io/FastAPI-boilerplate/user-guide/authentication/), logout via token blacklist -* **DB**: Postgres + SQLAlchemy 2.0, [Alembic migrations](https://benavlabs.github.io/FastAPI-boilerplate/user-guide/database/) -* **CRUD**: [FastCRUD generics](https://benavlabs.github.io/FastAPI-boilerplate/user-guide/database/crud/) (get, get_multi, create, update, delete, joins) -* **Caching**: [decorator-based endpoints cache](https://benavlabs.github.io/FastAPI-boilerplate/user-guide/caching/); client cache headers -* **Queues**: [ARQ worker](https://benavlabs.github.io/FastAPI-boilerplate/user-guide/background-tasks/) (async jobs), Redis connection helpers -* **Rate limits**: [per-tier + per-path rules](https://benavlabs.github.io/FastAPI-boilerplate/user-guide/rate-limiting/) -* **Admin**: [CRUDAdmin views](https://benavlabs.github.io/FastAPI-boilerplate/user-guide/admin-panel/) for common models (optional) +- **App**: FastAPI app factory, [env-aware docs](https://benavlabs.github.io/FastAPI-boilerplate/user-guide/development/) exposure +- **Auth**: [JWT access/refresh](https://benavlabs.github.io/FastAPI-boilerplate/user-guide/authentication/), logout via token blacklist +- **DB**: Postgres + SQLAlchemy 2.0, [Alembic migrations](https://benavlabs.github.io/FastAPI-boilerplate/user-guide/database/) +- **CRUD**: [FastCRUD generics](https://benavlabs.github.io/FastAPI-boilerplate/user-guide/database/crud/) (get, get_multi, create, update, delete, joins) +- **Caching**: [decorator-based endpoints cache](https://benavlabs.github.io/FastAPI-boilerplate/user-guide/caching/); client cache headers +- **Queues**: [ARQ worker](https://benavlabs.github.io/FastAPI-boilerplate/user-guide/background-tasks/) (async jobs), Redis connection helpers +- **Rate limits**: [per-tier + per-path rules](https://benavlabs.github.io/FastAPI-boilerplate/user-guide/rate-limiting/) +- **Admin**: [CRUDAdmin views](https://benavlabs.github.io/FastAPI-boilerplate/user-guide/admin-panel/) for common models (optional) This is what we've been using in production apps. Several applications running in production started from this boilerplate as their foundation - from SaaS platforms to internal tools. It's proven, stable technology that works together reliably. Use this as the foundation for whatever you want to build on top. > **Building an AI SaaS?** Skip even more setup with [**FastroAI**](https://fastro.ai) - our production-ready template with AI integration, payments, and frontend included. -## TL;DR - Quickstart - -Use the template on GitHub, create your repo, then: - -```bash -git clone https://github.com//FastAPI-boilerplate -cd FastAPI-boilerplate -``` - -**Quick setup:** Run the interactive setup script to choose your deployment configuration: - -```bash -./setup.py -``` - -Or directly specify the deployment type: `./setup.py local`, `./setup.py staging`, or `./setup.py production`. - -The script copies the right files for your deployment scenario. Here's what each option sets up: - -### Option 1: Local development with Uvicorn - -Best for: **Development and testing** - -**Copies:** - -- `scripts/local_with_uvicorn/Dockerfile` → `Dockerfile` -- `scripts/local_with_uvicorn/docker-compose.yml` → `docker-compose.yml` -- `scripts/local_with_uvicorn/.env.example` → `src/.env` - -Sets up Uvicorn with auto-reload enabled. The example environment values work fine for development. - -**Manual setup:** `./setup.py local` or copy the files above manually. - -### Option 2: Staging with Gunicorn managing Uvicorn workers - -Best for: **Staging environments and load testing** - -**Copies:** +## Quickstart -- `scripts/gunicorn_managing_uvicorn_workers/Dockerfile` → `Dockerfile` -- `scripts/gunicorn_managing_uvicorn_workers/docker-compose.yml` → `docker-compose.yml` -- `scripts/gunicorn_managing_uvicorn_workers/.env.example` → `src/.env` +1. Create your repository from this [template on GitHub](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-repository-from-a-template). -Sets up Gunicorn managing multiple Uvicorn workers for production-like performance testing. +1. Clone your repository. -> [!WARNING] -> Change `SECRET_KEY` and passwords in the `.env` file for staging environments. +1. Deploy it locally -**Manual setup:** `./setup.py staging` or copy the files above manually. + ``` + docker compose up + ``` -### Option 3: Production with NGINX - -Best for: **Production deployments** - -**Copies:** - -- `scripts/production_with_nginx/Dockerfile` → `Dockerfile` -- `scripts/production_with_nginx/docker-compose.yml` → `docker-compose.yml` -- `scripts/production_with_nginx/.env.example` → `src/.env` - -Sets up NGINX as reverse proxy with Gunicorn + Uvicorn workers for production. - -> [!CAUTION] -> You MUST change `SECRET_KEY`, all passwords, and sensitive values in the `.env` file before deploying! - -**Manual setup:** `./setup.py production` or copy the files above manually. - ---- - -**Start your application:** - -```bash -docker compose up -``` - -**Access your app:** -- **Local**: http://127.0.0.1:8000 (auto-reload enabled) → [API docs](http://127.0.0.1:8000/docs) -- **Staging**: http://127.0.0.1:8000 (production-like performance) -- **Production**: http://localhost (NGINX reverse proxy) +1. Open the API Swagger `http://localhost:8000/docs` ### Next steps **Create your first admin user:** + ```bash docker compose run --rm create_superuser ``` **Run database migrations** (if you add models): + ```bash cd src && uv run alembic revision --autogenerate && uv run alembic upgrade head ``` **Test background jobs:** -```bash -curl -X POST 'http://127.0.0.1:8000/api/v1/tasks/task?message=hello' -``` -**Or run locally without Docker:** ```bash -uv sync && uv run uvicorn src.app.main:app --reload +curl -X POST 'http://127.0.0.1:8000/api/v1/tasks/task?message=hello' ``` > Full setup (from-scratch, .env examples, PostgreSQL & Redis, gunicorn, nginx) lives in the [docs](https://benavlabs.github.io/FastAPI-boilerplate/getting-started/installation/). @@ -177,8 +111,8 @@ Create `src/.env` and set **app**, **database**, **JWT**, and **environment** se [https://benavlabs.github.io/FastAPI-boilerplate/getting-started/configuration/](https://benavlabs.github.io/FastAPI-boilerplate/getting-started/configuration/) -* `ENVIRONMENT=local|staging|production` controls API docs exposure -* Set `ADMIN_*` to enable the first admin user +- `ENVIRONMENT=local|staging|production` controls API docs exposure +- Set `ADMIN_*` to enable the first admin user ## Common tasks diff --git a/default.conf b/default.conf deleted file mode 100644 index a763f9fa..00000000 --- a/default.conf +++ /dev/null @@ -1,32 +0,0 @@ -# ---------------- Running With One Server ---------------- -server { - listen 80; - - location / { - proxy_pass http://web:8000; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} - - -# # ---------------- To Run with Multiple Servers, Uncomment below ---------------- -# upstream fastapi_app { -# server fastapi1:8000; # Replace with actual server names or IP addresses -# server fastapi2:8000; -# # Add more servers as needed -# } - -# server { -# listen 80; - -# location / { -# proxy_pass http://fastapi_app; -# proxy_set_header Host $host; -# proxy_set_header X-Real-IP $remote_addr; -# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; -# proxy_set_header X-Forwarded-Proto $scheme; -# } -# } diff --git a/scripts/local_with_uvicorn/docker-compose.yml b/docker-compose.yml similarity index 88% rename from scripts/local_with_uvicorn/docker-compose.yml rename to docker-compose.yml index e41c2c98..941e34bb 100644 --- a/scripts/local_with_uvicorn/docker-compose.yml +++ b/docker-compose.yml @@ -45,16 +45,6 @@ services: expose: - "6379" - #-------- uncomment to run with nginx -------- - # nginx: - # image: nginx:latest - # ports: - # - "80:80" - # volumes: - # - ./default.conf:/etc/nginx/conf.d/default.conf - # depends_on: - # - web - #-------- uncomment to create first superuser -------- create_superuser: build: diff --git a/docs/user-guide/configuration/environment-variables.md b/docs/user-guide/configuration/environment-variables.md index 53fd3ca6..1b256813 100644 --- a/docs/user-guide/configuration/environment-variables.md +++ b/docs/user-guide/configuration/environment-variables.md @@ -274,7 +274,6 @@ services: db: # PostgreSQL database redis: # Redis for caching/queues worker: # ARQ background task worker - nginx: # Reverse proxy (optional) ``` ## Python Settings Classes diff --git a/pyproject.toml b/pyproject.toml index f5b26924..080f84ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,6 @@ dependencies = [ "psycopg2-binary>=2.9.9", "fastcrud>=0.19.2", "crudadmin>=0.4.2", - "gunicorn>=23.0.0", "ruff>=0.11.13", "mypy>=1.16.0", ] diff --git a/scripts/gunicorn_managing_uvicorn_workers/.env.example b/scripts/gunicorn_managing_uvicorn_workers/.env.example deleted file mode 100644 index 84968a97..00000000 --- a/scripts/gunicorn_managing_uvicorn_workers/.env.example +++ /dev/null @@ -1,67 +0,0 @@ -# ============================================================================ -# WARNING: EXAMPLE CONFIGURATION - DO NOT USE IN PRODUCTION AS-IS -# ============================================================================ -# This file contains example values for development/testing purposes only. -# -# SECURITY CRITICAL: Before deploying to production, you MUST: -# 1. Copy this file to a .env file at the project root -# 2. Generate a new SECRET_KEY using: openssl rand -hex 32 -# 3. Change all passwords (POSTGRES_PASSWORD, ADMIN_PASSWORD, etc.) -# 4. Update all sensitive configuration values -# -# Using these example values in production is a SECURITY RISK. -# ============================================================================ - -# ------------- app settings ------------- -APP_NAME="My Project" -APP_DESCRIPTION="My Project Description" -APP_VERSION="0.1" -CONTACT_NAME="Me" -CONTACT_EMAIL="my.email@example.com" -LICENSE_NAME="MIT" - -# ------------- database ------------- -POSTGRES_USER="postgres" -POSTGRES_PASSWORD=1234 -POSTGRES_SERVER="db" -POSTGRES_PORT=5432 -POSTGRES_DB="postgres" -POSTGRES_ASYNC_PREFIX="postgresql+asyncpg://" - -# ------------- crypt ------------- -SECRET_KEY=953843cd400d99a039698e7feb46ca1b3e33c44fee2c24c6d88cf0f0b290fb61 -ALGORITHM=HS256 -ACCESS_TOKEN_EXPIRE_MINUTES=60 - -# ------------- admin ------------- -ADMIN_NAME="admin" -ADMIN_EMAIL="admin@example.com" -ADMIN_USERNAME="admin" -ADMIN_PASSWORD="Str1ngst!" - -# ------------- redis cache ------------- -REDIS_CACHE_HOST="redis" -REDIS_CACHE_PORT=6379 - -# ------------- redis queue ------------- -REDIS_QUEUE_HOST="redis" -REDIS_QUEUE_PORT=6379 - -# ------------- redis rate limit ------------- -REDIS_RATE_LIMIT_HOST="redis" -REDIS_RATE_LIMIT_PORT=6379 - -# ------------- client side cache ------------- -CLIENT_CACHE_MAX_AGE=60 - -# ------------- test ------------- -TEST_NAME="Tester User" -TEST_EMAIL="test@tester.com" -TEST_USERNAME="testeruser" -TEST_PASSWORD="Str1ngT3st!" - -# ------------- environment ------------- -ENVIRONMENT="staging" - -# ------------- first tier ------------- -TIER_NAME="free" diff --git a/scripts/gunicorn_managing_uvicorn_workers/Dockerfile b/scripts/gunicorn_managing_uvicorn_workers/Dockerfile deleted file mode 100644 index 98d55fcf..00000000 --- a/scripts/gunicorn_managing_uvicorn_workers/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -# --------- requirements --------- - -FROM python:3.11 as requirements-stage - -WORKDIR /tmp - -RUN pip install poetry - -COPY ./pyproject.toml ./poetry.lock* /tmp/ - -RUN poetry export -f requirements.txt --output requirements.txt --without-hashes - - -# --------- final image build --------- -FROM python:3.11 - -WORKDIR /code - -COPY --from=requirements-stage /tmp/requirements.txt /code/requirements.txt - -RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt - -COPY ./src/app /code/app - -# -------- replace with comment to run with gunicorn -------- -# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] -CMD ["gunicorn", "app.main:app", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "-b", "0.0.0.0:8000"] diff --git a/scripts/gunicorn_managing_uvicorn_workers/docker-compose.yml b/scripts/gunicorn_managing_uvicorn_workers/docker-compose.yml deleted file mode 100644 index 8ec38e87..00000000 --- a/scripts/gunicorn_managing_uvicorn_workers/docker-compose.yml +++ /dev/null @@ -1,111 +0,0 @@ -services: - web: - build: - context: . - dockerfile: Dockerfile - # -------- Both of the following commands should be commented to run with nginx -------- - - # -------- replace with comment to run with gunicorn or just uvicorn -------- - # command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload - command: gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000 - env_file: - - .env - # -------- replace with expose if you are using nginx -------- - ports: - - "8000:8000" - # expose: - # - "8000" - depends_on: - - db - - redis - volumes: - - ./src/app:/code/app - - .env:/.env - - worker: - build: - context: . - dockerfile: Dockerfile - command: arq app.core.worker.settings.WorkerSettings - env_file: - - .env - depends_on: - - db - - redis - volumes: - - ./src/app:/code/app - - .env:/.env - - db: - image: postgres:13 - env_file: - - .env - volumes: - - postgres-data:/var/lib/postgresql/data - expose: - - "5432" - - redis: - image: redis:alpine - volumes: - - redis-data:/data - expose: - - "6379" - - #-------- uncomment to run with nginx -------- - # nginx: - # image: nginx:latest - # ports: - # - "80:80" - # volumes: - # - ./default.conf:/etc/nginx/conf.d/default.conf - # depends_on: - # - web - - #-------- uncomment to create first superuser -------- - create_superuser: - build: - context: . - dockerfile: Dockerfile - env_file: - - .env - depends_on: - - db - - web - command: python -m src.scripts.create_first_superuser - volumes: - - ./src:/code/src - - #-------- uncomment to run tests -------- - # pytest: - # build: - # context: . - # dockerfile: Dockerfile - # env_file: - # - .env - # depends_on: - # - db - # - create_superuser - # - redis - # command: python -m pytest ./tests - # volumes: - # - .:/code - - #-------- uncomment to create first tier -------- - # create_tier: - # build: - # context: . - # dockerfile: Dockerfile - # env_file: - # - .env - # depends_on: - # - create_superuser - # - db - # - web - # command: python -m src.scripts.create_first_tier - # volumes: - # - ./src:/code/src - -volumes: - postgres-data: - redis-data: diff --git a/scripts/production_with_nginx/.env.example b/scripts/production_with_nginx/.env.example deleted file mode 100644 index faacc1aa..00000000 --- a/scripts/production_with_nginx/.env.example +++ /dev/null @@ -1,67 +0,0 @@ -# ============================================================================ -# WARNING: EXAMPLE CONFIGURATION - DO NOT USE IN PRODUCTION AS-IS -# ============================================================================ -# This file contains example values for development/testing purposes only. -# -# SECURITY CRITICAL: Before deploying to production, you MUST: -# 1. Copy this file to a .env file at the project root -# 2. Generate a new SECRET_KEY using: openssl rand -hex 32 -# 3. Change all passwords (POSTGRES_PASSWORD, ADMIN_PASSWORD, etc.) -# 4. Update all sensitive configuration values -# -# Using these example values in production is a SECURITY RISK. -# ============================================================================ - -# ------------- app settings ------------- -APP_NAME="My Project" -APP_DESCRIPTION="My Project Description" -APP_VERSION="0.1" -CONTACT_NAME="Me" -CONTACT_EMAIL="my.email@example.com" -LICENSE_NAME="MIT" - -# ------------- database ------------- -POSTGRES_USER="postgres" -POSTGRES_PASSWORD=1234 -POSTGRES_SERVER="db" -POSTGRES_PORT=5432 -POSTGRES_DB="postgres" -POSTGRES_ASYNC_PREFIX="postgresql+asyncpg://" - -# ------------- crypt ------------- -SECRET_KEY=db210482bea9aae930b00b17f3449a21340c281ac7e1f2a4e33e2c5cd77f291e -ALGORITHM=HS256 -ACCESS_TOKEN_EXPIRE_MINUTES=60 - -# ------------- admin ------------- -ADMIN_NAME="admin" -ADMIN_EMAIL="admin@example.com" -ADMIN_USERNAME="admin" -ADMIN_PASSWORD="Str1ngst!" - -# ------------- redis cache ------------- -REDIS_CACHE_HOST="redis" -REDIS_CACHE_PORT=6379 - -# ------------- redis queue ------------- -REDIS_QUEUE_HOST="redis" -REDIS_QUEUE_PORT=6379 - -# ------------- redis rate limit ------------- -REDIS_RATE_LIMIT_HOST="redis" -REDIS_RATE_LIMIT_PORT=6379 - -# ------------- client side cache ------------- -CLIENT_CACHE_MAX_AGE=60 - -# ------------- test ------------- -TEST_NAME="Tester User" -TEST_EMAIL="test@tester.com" -TEST_USERNAME="testeruser" -TEST_PASSWORD="Str1ngT3st!" - -# ------------- environment ------------- -ENVIRONMENT="production" - -# ------------- first tier ------------- -TIER_NAME="free" diff --git a/scripts/production_with_nginx/Dockerfile b/scripts/production_with_nginx/Dockerfile deleted file mode 100644 index 8b8ccfee..00000000 --- a/scripts/production_with_nginx/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -# --------- requirements --------- - -FROM python:3.11 as requirements-stage - -WORKDIR /tmp - -RUN pip install poetry - -COPY ./pyproject.toml ./poetry.lock* /tmp/ - -RUN poetry export -f requirements.txt --output requirements.txt --without-hashes - - -# --------- final image build --------- -FROM python:3.11 - -WORKDIR /code - -COPY --from=requirements-stage /tmp/requirements.txt /code/requirements.txt - -RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt - -COPY ./src/app /code/app - -# -------- replace with comment to run with gunicorn -------- -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] -# CMD ["gunicorn", "app.main:app", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "-b", "0.0.0.0:8000"] diff --git a/scripts/production_with_nginx/docker-compose.yml b/scripts/production_with_nginx/docker-compose.yml deleted file mode 100644 index 9ed7f9ae..00000000 --- a/scripts/production_with_nginx/docker-compose.yml +++ /dev/null @@ -1,110 +0,0 @@ -services: - web: - build: - context: . - dockerfile: Dockerfile - # -------- Both of the following commands should be commented to run with nginx -------- - - # -------- replace with comment to run with gunicorn -------- - # command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload - command: gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000 - env_file: - - .env - # -------- replace ports with expose if you are using nginx -------- - # ports: - # - "8000:8000" - expose: - - "8000" - depends_on: - - db - - redis - volumes: - - ./src/app:/code/app - - .env:/.env - - worker: - build: - context: . - dockerfile: Dockerfile - command: arq app.core.worker.settings.WorkerSettings - env_file: - - .env - depends_on: - - db - - redis - volumes: - - ./src/app:/code/app - - .env:/.env - - db: - image: postgres:13 - env_file: - - .env - volumes: - - postgres-data:/var/lib/postgresql/data - expose: - - "5432" - - redis: - image: redis:alpine - volumes: - - redis-data:/data - expose: - - "6379" - - #-------- uncomment to run with nginx -------- - nginx: - image: nginx:latest - ports: - - "80:80" - volumes: - - ./default.conf:/etc/nginx/conf.d/default.conf - depends_on: - - web - - #-------- uncomment to create first superuser -------- - # create_superuser: - # build: - # context: . - # dockerfile: Dockerfile - # env_file: - # - .env - # depends_on: - # - db - # - web - # command: python -m src.scripts.create_first_superuser - # volumes: - # - ./src:/code/src - - #-------- uncomment to run tests -------- - # pytest: - # build: - # context: . - # dockerfile: Dockerfile - # env_file: - # - .env - # depends_on: - # - web - # - redis - # command: python -m pytest ./tests - # volumes: - # - .:/code - - #-------- uncomment to create first tier -------- - # create_tier: - # build: - # context: . - # dockerfile: Dockerfile - # env_file: - # - .env - # depends_on: - # - create_superuser - # - db - # - web - # command: python -m src.scripts.create_first_tier - # volumes: - # - ./src:/code/src - -volumes: - postgres-data: - redis-data: diff --git a/setup.py b/setup.py deleted file mode 100755 index 25b720f4..00000000 --- a/setup.py +++ /dev/null @@ -1,176 +0,0 @@ -#!/usr/bin/env python3 -"""FastAPI Boilerplate Setup Script. - -Automates copying the correct configuration files for different deployment scenarios. -""" - -import shutil -import sys -from pathlib import Path - -DEPLOYMENTS = { - "local": { - "name": "Local development with Uvicorn", - "description": "Auto-reload enabled, development-friendly", - "path": "scripts/local_with_uvicorn", - }, - "staging": { - "name": "Staging with Gunicorn managing Uvicorn workers", - "description": "Production-like setup for testing", - "path": "scripts/gunicorn_managing_uvicorn_workers", - }, - "production": { - "name": "Production with NGINX", - "description": "Full production setup with reverse proxy", - "path": "scripts/production_with_nginx", - }, -} - - -def show_help(): - """Display help information.""" - print("FastAPI Boilerplate Setup") - print("=" * 25) - print() - print("Usage: python setup.py ") - print() - print("Available deployment types:") - for key, config in DEPLOYMENTS.items(): - print(f" {key:12} - {config['name']}") - print(f" {' ' * 12} {config['description']}") - print() - print("Examples:") - print(" python setup.py local # Set up for local development") - print(" python setup.py staging # Set up for staging environment") - print(" python setup.py production # Set up for production deployment") - - -def copy_files(deployment_type: str): - """Copy configuration files for the specified deployment type.""" - if deployment_type not in DEPLOYMENTS: - print(f"āŒ Unknown deployment type: {deployment_type}") - print() - show_help() - return False - - config = DEPLOYMENTS[deployment_type] - source_path = Path(config["path"]) - - if not source_path.exists(): - print(f"āŒ Configuration path not found: {source_path}") - return False - - print(f"šŸš€ Setting up {config['name']}...") - print(f" {config['description']}") - print() - - files_to_copy = [ - ("Dockerfile", "Dockerfile"), - ("docker-compose.yml", "docker-compose.yml"), - (".env.example", ".env"), - ] - - success = True - for source_file, dest_file in files_to_copy: - source = source_path / source_file - dest = Path(dest_file) - - if not source.exists(): - print(f"āš ļø Warning: {source} not found, skipping...") - continue - - try: - dest.parent.mkdir(parents=True, exist_ok=True) - - shutil.copy2(source, dest) - print(f"āœ… Copied {source} → {dest}") - - except Exception as e: - print(f"āŒ Failed to copy {source} → {dest}: {e}") - success = False - - if success: - print() - print("šŸŽ‰ Setup complete!") - print() - - if deployment_type in ["staging", "production"]: - print("āš ļø IMPORTANT: Update the .env file with your production values:") - print(" - Generate a new SECRET_KEY: openssl rand -hex 32") - print(" - Change all passwords and sensitive values") - print() - - print("Next steps:") - print(" docker compose up") - - if deployment_type == "local": - print(" open http://127.0.0.1:8000/docs") - elif deployment_type == "production": - print(" open http://localhost") - - return True - - return False - - -def interactive_setup(): - """Interactive setup when no arguments provided.""" - print("FastAPI Boilerplate Setup") - print("=" * 25) - print() - print("Choose your deployment type:") - print() - - options = list(DEPLOYMENTS.keys()) - for i, key in enumerate(options, 1): - config = DEPLOYMENTS[key] - print(f" {i}. {config['name']}") - print(f" {config['description']}") - print() - - while True: - try: - choice = input(f"Enter your choice (1-{len(options)}): ").strip() - - if choice.isdigit(): - choice_num = int(choice) - if 1 <= choice_num <= len(options): - return options[choice_num - 1] - - if choice.lower() in DEPLOYMENTS: - return choice.lower() - - print(f"āŒ Invalid choice. Please enter 1-{len(options)} or the deployment name.") - - except KeyboardInterrupt: - print("\n\nšŸ‘‹ Setup cancelled.") - return None - except EOFError: - print("\n\nšŸ‘‹ Setup cancelled.") - return None - - -def main(): - """Main entry point.""" - if len(sys.argv) > 1 and sys.argv[1] in ["-h", "--help", "help"]: - show_help() - return - - if len(sys.argv) == 2: - deployment_type = sys.argv[1].lower() - elif len(sys.argv) == 1: - deployment_type = interactive_setup() - if deployment_type is None: - return - else: - show_help() - return - - success = copy_files(deployment_type) - - if not success: - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/uv.lock b/uv.lock index 5dda7a25..db4f5c4e 100644 --- a/uv.lock +++ b/uv.lock @@ -389,7 +389,6 @@ dependencies = [ { name = "fastapi" }, { name = "fastcrud" }, { name = "greenlet" }, - { name = "gunicorn" }, { name = "httptools" }, { name = "httpx" }, { name = "mypy" }, @@ -437,7 +436,6 @@ requires-dist = [ { name = "fastapi", specifier = ">=0.109.1" }, { name = "fastcrud", specifier = ">=0.19.2" }, { name = "greenlet", specifier = ">=2.0.2" }, - { name = "gunicorn", specifier = ">=23.0.0" }, { name = "httptools", specifier = ">=0.6.1" }, { name = "httpx", specifier = ">=0.26.0" }, { name = "mypy", specifier = ">=1.16.0" }, @@ -536,18 +534,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5c/4f/aab73ecaa6b3086a4c89863d94cf26fa84cbff63f52ce9bc4342b3087a06/greenlet-3.2.3-cp314-cp314-win_amd64.whl", hash = "sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a", size = 301236, upload-time = "2025-06-05T16:15:20.111Z" }, ] -[[package]] -name = "gunicorn" -version = "23.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031, upload-time = "2024-08-10T20:25:27.378Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" }, -] - [[package]] name = "h11" version = "0.16.0"