mirror of
https://github.com/fastapi/full-stack-fastapi-template.git
synced 2026-11-03 20:36:27 +00:00
Compare commits
7 Commits
5217a7e693
..
0.11.1
| Author | SHA1 | Date | |
|---|---|---|---|
| ddb3c34f19 | |||
| eae2d6701d | |||
| 6b9fa4113c | |||
| b55b3122ab | |||
| 6dbb2e8256 | |||
| 6d75b2d132 | |||
| 32ab6dd022 |
@@ -14,7 +14,5 @@ SMTP_TLS=False
|
|||||||
SMTP_PORT=1025
|
SMTP_PORT=1025
|
||||||
|
|
||||||
# Postgres
|
# Postgres
|
||||||
POSTGRES_SERVER=localhost
|
|
||||||
POSTGRES_DB=app
|
|
||||||
POSTGRES_USER=postgres
|
|
||||||
POSTGRES_PASSWORD=changethis
|
POSTGRES_PASSWORD=changethis
|
||||||
|
DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@localhost:5432/app
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
name: Deploy to Production
|
|
||||||
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types:
|
|
||||||
- published
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
environment: production
|
|
||||||
# Do not deploy in the main repository, only in user projects
|
|
||||||
if: github.repository_owner != 'fastapi'
|
|
||||||
runs-on:
|
|
||||||
- self-hosted
|
|
||||||
- production
|
|
||||||
env:
|
|
||||||
DOMAIN: ${{ secrets.DOMAIN }}
|
|
||||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
|
||||||
FIRST_SUPERUSER: ${{ secrets.FIRST_SUPERUSER }}
|
|
||||||
FIRST_SUPERUSER_PASSWORD: ${{ secrets.FIRST_SUPERUSER_PASSWORD }}
|
|
||||||
SMTP_HOST: ${{ secrets.SMTP_HOST }}
|
|
||||||
SMTP_USER: ${{ secrets.SMTP_USER }}
|
|
||||||
SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }}
|
|
||||||
EMAILS_FROM_EMAIL: ${{ secrets.EMAILS_FROM_EMAIL }}
|
|
||||||
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
|
|
||||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
- run: docker compose -f compose.yml -f compose.deploy.yml build
|
|
||||||
- run: docker compose -f compose.yml -f compose.deploy.yml run --rm backend bash scripts/prestart.sh
|
|
||||||
- run: docker compose -f compose.yml -f compose.deploy.yml up -d
|
|
||||||
@@ -1,21 +1,20 @@
|
|||||||
name: Deploy to Staging
|
name: Deploy
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
paths-ignore:
|
||||||
|
- release-notes.md
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
environment: staging
|
|
||||||
# Do not deploy in the main repository, only in user projects
|
# Do not deploy in the main repository, only in user projects
|
||||||
if: github.repository_owner != 'fastapi'
|
if: github.repository_owner != 'fastapi'
|
||||||
runs-on:
|
runs-on: self-hosted
|
||||||
- self-hosted
|
|
||||||
- staging
|
|
||||||
env:
|
env:
|
||||||
DOMAIN: ${{ secrets.DOMAIN }}
|
DOMAIN: ${{ secrets.DOMAIN }}
|
||||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||||
@@ -31,7 +31,7 @@ target_metadata = SQLModel.metadata
|
|||||||
|
|
||||||
|
|
||||||
def get_url():
|
def get_url():
|
||||||
return str(settings.SQLALCHEMY_DATABASE_URI)
|
return str(settings.DATABASE_URL)
|
||||||
|
|
||||||
|
|
||||||
def run_migrations_offline():
|
def run_migrations_offline():
|
||||||
|
|||||||
+12
-17
@@ -6,6 +6,7 @@ from pydantic import (
|
|||||||
HttpUrl,
|
HttpUrl,
|
||||||
PostgresDsn,
|
PostgresDsn,
|
||||||
computed_field,
|
computed_field,
|
||||||
|
field_validator,
|
||||||
model_validator,
|
model_validator,
|
||||||
)
|
)
|
||||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
@@ -27,23 +28,16 @@ class Settings(BaseSettings):
|
|||||||
|
|
||||||
PROJECT_NAME: str
|
PROJECT_NAME: str
|
||||||
SENTRY_DSN: HttpUrl | None = None
|
SENTRY_DSN: HttpUrl | None = None
|
||||||
POSTGRES_SERVER: str
|
DATABASE_URL: PostgresDsn
|
||||||
POSTGRES_PORT: int = 5432
|
|
||||||
POSTGRES_USER: str
|
|
||||||
POSTGRES_PASSWORD: str = ""
|
|
||||||
POSTGRES_DB: str = ""
|
|
||||||
|
|
||||||
@computed_field # type: ignore[prop-decorator]
|
@field_validator("DATABASE_URL", mode="before")
|
||||||
@property
|
@classmethod
|
||||||
def SQLALCHEMY_DATABASE_URI(self) -> PostgresDsn:
|
def _use_psycopg_driver(cls, value: str | PostgresDsn) -> str:
|
||||||
return PostgresDsn.build(
|
database_url = str(value)
|
||||||
scheme="postgresql+psycopg",
|
for scheme in ("postgres://", "postgresql://"):
|
||||||
username=self.POSTGRES_USER,
|
if database_url.startswith(scheme):
|
||||||
password=self.POSTGRES_PASSWORD,
|
return database_url.replace(scheme, "postgresql+psycopg://", 1)
|
||||||
host=self.POSTGRES_SERVER,
|
return database_url
|
||||||
port=self.POSTGRES_PORT,
|
|
||||||
path=self.POSTGRES_DB,
|
|
||||||
)
|
|
||||||
|
|
||||||
SMTP_TLS: bool = True
|
SMTP_TLS: bool = True
|
||||||
SMTP_SSL: bool = False
|
SMTP_SSL: bool = False
|
||||||
@@ -85,7 +79,8 @@ class Settings(BaseSettings):
|
|||||||
@model_validator(mode="after")
|
@model_validator(mode="after")
|
||||||
def _enforce_non_default_secrets(self) -> Self:
|
def _enforce_non_default_secrets(self) -> Self:
|
||||||
self._check_default_secret("SECRET_KEY", self.SECRET_KEY)
|
self._check_default_secret("SECRET_KEY", self.SECRET_KEY)
|
||||||
self._check_default_secret("POSTGRES_PASSWORD", self.POSTGRES_PASSWORD)
|
for host in self.DATABASE_URL.hosts():
|
||||||
|
self._check_default_secret("DATABASE_URL password", host["password"])
|
||||||
self._check_default_secret(
|
self._check_default_secret(
|
||||||
"FIRST_SUPERUSER_PASSWORD", self.FIRST_SUPERUSER_PASSWORD
|
"FIRST_SUPERUSER_PASSWORD", self.FIRST_SUPERUSER_PASSWORD
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from app import crud
|
|||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.models import User, UserCreate
|
from app.models import User, UserCreate
|
||||||
|
|
||||||
engine = create_engine(str(settings.SQLALCHEMY_DATABASE_URI))
|
engine = create_engine(str(settings.DATABASE_URL))
|
||||||
|
|
||||||
|
|
||||||
# make sure all SQLModel models are imported (app.models) before initializing DB
|
# make sure all SQLModel models are imported (app.models) before initializing DB
|
||||||
|
|||||||
+3
-7
@@ -20,7 +20,7 @@ services:
|
|||||||
db:
|
db:
|
||||||
image: postgres:18
|
image: postgres:18
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
test: ["CMD-SHELL", "pg_isready -U postgres -d app"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
retries: 5
|
retries: 5
|
||||||
start_period: 30s
|
start_period: 30s
|
||||||
@@ -29,8 +29,7 @@ services:
|
|||||||
- app-db-data:/var/lib/postgresql
|
- app-db-data:/var/lib/postgresql
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?Variable not set}
|
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?Variable not set}
|
||||||
- POSTGRES_USER=${POSTGRES_USER:?Variable not set}
|
- POSTGRES_DB=app
|
||||||
- POSTGRES_DB=${POSTGRES_DB:?Variable not set}
|
|
||||||
|
|
||||||
adminer:
|
adminer:
|
||||||
image: adminer
|
image: adminer
|
||||||
@@ -62,10 +61,7 @@ services:
|
|||||||
SMTP_USER: ${SMTP_USER:-}
|
SMTP_USER: ${SMTP_USER:-}
|
||||||
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
|
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
|
||||||
EMAILS_FROM_EMAIL: ${EMAILS_FROM_EMAIL}
|
EMAILS_FROM_EMAIL: ${EMAILS_FROM_EMAIL}
|
||||||
POSTGRES_SERVER: db
|
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:?Variable not set}@db:5432/app
|
||||||
POSTGRES_DB: ${POSTGRES_DB:?Variable not set}
|
|
||||||
POSTGRES_USER: ${POSTGRES_USER:?Variable not set}
|
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Variable not set}
|
|
||||||
SENTRY_DSN: ${SENTRY_DSN:-}
|
SENTRY_DSN: ${SENTRY_DSN:-}
|
||||||
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
|
|||||||
+12
-50
@@ -4,13 +4,13 @@ You can deploy the project using Docker Compose to a remote server.
|
|||||||
|
|
||||||
The deployment Docker Compose configuration includes Traefik to handle HTTPS and route incoming traffic to the application.
|
The deployment Docker Compose configuration includes Traefik to handle HTTPS and route incoming traffic to the application.
|
||||||
|
|
||||||
You can use CI/CD (continuous integration and continuous deployment) systems to deploy automatically, there are already configurations to do it with GitHub Actions.
|
The included GitHub Actions workflow can deploy the application automatically.
|
||||||
|
|
||||||
But you have to configure a couple things first. 🤓
|
But you have to configure a couple things first. 🤓
|
||||||
|
|
||||||
## Preparation
|
## Preparation
|
||||||
|
|
||||||
* Have a remote server ready and available. Use a separate server for each environment, for example one for staging and one for production.
|
* Have a remote server ready and available.
|
||||||
* Configure DNS records pointing to the server for the application domain and any supporting service subdomains you want to expose, e.g. `fastapi-project.example.com` and `adminer.fastapi-project.example.com`.
|
* Configure DNS records pointing to the server for the application domain and any supporting service subdomains you want to expose, e.g. `fastapi-project.example.com` and `adminer.fastapi-project.example.com`.
|
||||||
* Install and configure [Docker](https://docs.docker.com/engine/install/) on the remote server (Docker Engine, not Docker Desktop).
|
* Install and configure [Docker](https://docs.docker.com/engine/install/) on the remote server (Docker Engine, not Docker Desktop).
|
||||||
|
|
||||||
@@ -80,16 +80,17 @@ You can set several other environment variables:
|
|||||||
* `SMTP_USER`: The SMTP server user to send emails.
|
* `SMTP_USER`: The SMTP server user to send emails.
|
||||||
* `SMTP_PASSWORD`: The SMTP server password to send emails.
|
* `SMTP_PASSWORD`: The SMTP server password to send emails.
|
||||||
* `EMAILS_FROM_EMAIL`: The email account to send emails from.
|
* `EMAILS_FROM_EMAIL`: The email account to send emails from.
|
||||||
* `POSTGRES_USER`: The Postgres user, you can leave the default.
|
|
||||||
* `POSTGRES_DB`: The database name to use for this application. You can leave the default of `app`.
|
|
||||||
* `SENTRY_DSN`: The DSN for Sentry, if you are using it.
|
* `SENTRY_DSN`: The DSN for Sentry, if you are using it.
|
||||||
|
|
||||||
## GitHub Actions Environment Variables
|
## GitHub Repository Automation
|
||||||
|
|
||||||
There are some environment variables only used by GitHub Actions that you can configure:
|
Install the following GitHub Apps to enable the included repository automation:
|
||||||
|
|
||||||
* `LATEST_CHANGES`: Used by the GitHub Action [latest-changes](https://github.com/tiangolo/latest-changes) to automatically add release notes based on the PRs merged. It's a personal access token, read the docs for details.
|
* [Latest Changes](https://github.com/apps/latest-changes) updates `release-notes.md` when a pull request is merged.
|
||||||
* `SMOKESHOW_AUTH_KEY`: Used to handle and publish the code coverage using [Smokeshow](https://github.com/samuelcolvin/smokeshow), follow their instructions to create a (free) Smokeshow key.
|
* [PR Push](https://github.com/apps/pr-push) lets the pre-commit workflow push automated fixes to pull request branches.
|
||||||
|
* [PR Submit](https://github.com/apps/pr-submit) lets the **Bump pre-commit hooks** and **Prepare Release** workflows create pull requests.
|
||||||
|
|
||||||
|
To publish code coverage with [Smokeshow](https://github.com/samuelcolvin/smokeshow), add `SMOKESHOW_AUTH_KEY` as a repository secret.
|
||||||
|
|
||||||
### Deploy with Docker Compose
|
### Deploy with Docker Compose
|
||||||
|
|
||||||
@@ -108,7 +109,7 @@ The `compose.deploy.yml` file adds the deployment settings to the shared configu
|
|||||||
|
|
||||||
You can use GitHub Actions to deploy your project automatically. 😎
|
You can use GitHub Actions to deploy your project automatically. 😎
|
||||||
|
|
||||||
There are already two environment deployments configured, `staging` and `production`. Each environment should be deployed to a separate server. 🚀
|
The included `deploy.yml` workflow deploys the application whenever changes are pushed to `master`, including when a pull request is merged. 🚀
|
||||||
|
|
||||||
### Install GitHub Actions Runner
|
### Install GitHub Actions Runner
|
||||||
|
|
||||||
@@ -138,8 +139,6 @@ cd
|
|||||||
|
|
||||||
* [Install a GitHub Action self-hosted runner following the official guide](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/adding-self-hosted-runners#adding-a-self-hosted-runner-to-a-repository).
|
* [Install a GitHub Action self-hosted runner following the official guide](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/adding-self-hosted-runners#adding-a-self-hosted-runner-to-a-repository).
|
||||||
|
|
||||||
* When asked about labels, add a label for the environment, e.g. `production`. You can also add labels later.
|
|
||||||
|
|
||||||
After installing, the guide would tell you to run a command to start the runner. Nevertheless, it would stop once you terminate that process or if your local connection to your server is lost.
|
After installing, the guide would tell you to run a command to start the runner. Nevertheless, it would stop once you terminate that process or if your local connection to your server is lost.
|
||||||
|
|
||||||
To make sure it runs on startup and continues running, you can install it as a service. To do that, exit the `github` user and go back to the `root` user:
|
To make sure it runs on startup and continues running, you can install it as a service. To do that, exit the `github` user and go back to the `root` user:
|
||||||
@@ -182,17 +181,9 @@ cd /home/github/actions-runner
|
|||||||
|
|
||||||
You can read more about it in the official guide: [Configuring the self-hosted runner application as a service](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/configuring-the-self-hosted-runner-application-as-a-service).
|
You can read more about it in the official guide: [Configuring the self-hosted runner application as a service](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/configuring-the-self-hosted-runner-application-as-a-service).
|
||||||
|
|
||||||
### Configure GitHub Environments
|
### Set Repository Secrets
|
||||||
|
|
||||||
The deployment workflows use [GitHub Environments](https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments) for `staging` and `production`. This enables environment-specific secrets, deployment protection rules (e.g. required reviewers, wait timers), and deployment status tracking.
|
In your repository, go to **Settings** > **Secrets and variables** > **Actions** and add the following [repository secrets](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets#creating-secrets-for-a-repository):
|
||||||
|
|
||||||
To configure them, go to your repository's **Settings** > **Environments** and create the `staging` and `production` environments.
|
|
||||||
|
|
||||||
### Set Secrets
|
|
||||||
|
|
||||||
For each GitHub Environment (`staging` and `production`), configure the required secrets as [environment secrets](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets#creating-secrets-for-an-environment). Environment secrets are preferred over [repository secrets](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets#creating-secrets-for-a-repository) because they are scoped to the specific environment, reducing exposure and aligning with any protection rules you configure.
|
|
||||||
|
|
||||||
The deployment workflows require these secrets:
|
|
||||||
|
|
||||||
* `DOMAIN`
|
* `DOMAIN`
|
||||||
* `FIRST_SUPERUSER`
|
* `FIRST_SUPERUSER`
|
||||||
@@ -209,41 +200,12 @@ To enable emails, configure these additional secrets with the values from your e
|
|||||||
|
|
||||||
To enable Sentry, configure the `SENTRY_DSN` secret.
|
To enable Sentry, configure the `SENTRY_DSN` secret.
|
||||||
|
|
||||||
## GitHub Action Deployment Workflows
|
|
||||||
|
|
||||||
There are GitHub Action workflows in the `.github/workflows` directory already configured for deploying to the environments (GitHub Actions runners with the labels):
|
|
||||||
|
|
||||||
* `staging`: after pushing (or merging) to the branch `master`.
|
|
||||||
* `production`: after publishing a release.
|
|
||||||
|
|
||||||
Both workflows are associated with their respective GitHub Environments, so deployments will be visible in the repository's **Environments** section and will respect any protection rules you configure.
|
|
||||||
|
|
||||||
### Prepare a Release
|
|
||||||
|
|
||||||
Install the [PR Submit GitHub App](https://github.com/apps/pr-submit) in your repository to enable the **Prepare Release** workflow.
|
|
||||||
|
|
||||||
Run the workflow manually from the **Actions** tab and select the version bump. It creates a pull request that updates the release notes. When you merge that pull request, the **Create Draft Release** workflow creates a draft GitHub release with the corresponding release notes.
|
|
||||||
|
|
||||||
Review and publish the draft release to trigger the production deployment.
|
|
||||||
|
|
||||||
If you need to add extra environments you could use those as a starting point.
|
|
||||||
|
|
||||||
## URLs
|
## URLs
|
||||||
|
|
||||||
Replace `fastapi-project.example.com` with your domain.
|
Replace `fastapi-project.example.com` with your domain.
|
||||||
|
|
||||||
### Production
|
|
||||||
|
|
||||||
Application (frontend and API): `https://fastapi-project.example.com`
|
Application (frontend and API): `https://fastapi-project.example.com`
|
||||||
|
|
||||||
Interactive API docs: `https://fastapi-project.example.com/docs`
|
Interactive API docs: `https://fastapi-project.example.com/docs`
|
||||||
|
|
||||||
Adminer: `https://adminer.fastapi-project.example.com`
|
Adminer: `https://adminer.fastapi-project.example.com`
|
||||||
|
|
||||||
### Staging
|
|
||||||
|
|
||||||
Application (frontend and API): `https://staging.fastapi-project.example.com`
|
|
||||||
|
|
||||||
Interactive API docs: `https://staging.fastapi-project.example.com/docs`
|
|
||||||
|
|
||||||
Adminer: `https://adminer.staging.fastapi-project.example.com`
|
|
||||||
|
|||||||
+1
-1
@@ -156,7 +156,7 @@ biome check..............................................................Passed
|
|||||||
|
|
||||||
## URLs
|
## URLs
|
||||||
|
|
||||||
The production or staging URLs would use these same paths, but with your own domain.
|
The deployed URLs use these same paths, but with your own domain.
|
||||||
|
|
||||||
### Development URLs
|
### Development URLs
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,17 @@
|
|||||||
|
|
||||||
## Latest Changes
|
## Latest Changes
|
||||||
|
|
||||||
|
## 0.11.1 (2026-08-11)
|
||||||
|
|
||||||
|
### Refactors
|
||||||
|
|
||||||
|
* ♻️ Simplify Docker Compose deployment. PR [#2435](https://github.com/fastapi/full-stack-fastapi-template/pull/2435) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ♻️ Migrate to `DATABASE_URL` instead of separate variables. PR [#2184](https://github.com/fastapi/full-stack-fastapi-template/pull/2184) by [@patrick91](https://github.com/patrick91).
|
||||||
|
|
||||||
|
### Internal
|
||||||
|
|
||||||
|
* ⬆ Bump the npm-packages group across 1 directory with 33 updates. PR [#2433](https://github.com/fastapi/full-stack-fastapi-template/pull/2433) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
|
||||||
## 0.11.0 (2026-08-11)
|
## 0.11.0 (2026-08-11)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
Reference in New Issue
Block a user