chore: remove traefik/copier setup and simplify compose workflow
All checks were successful
Deploy to Production / deploy (push) Successful in 38s
All checks were successful
Deploy to Production / deploy (push) Successful in 38s
This commit is contained in:
@@ -1 +0,0 @@
|
|||||||
{{ _copier_answers|to_json -}}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
from pathlib import Path
|
|
||||||
import json
|
|
||||||
|
|
||||||
# Update the .env file with the answers from the .copier-answers.yml file
|
|
||||||
# without using Jinja2 templates in the .env file, this way the code works as is
|
|
||||||
# without needing Copier, but if Copier is used, the .env file will be updated
|
|
||||||
root_path = Path(__file__).parent.parent
|
|
||||||
answers_path = Path(__file__).parent / ".copier-answers.yml"
|
|
||||||
answers = json.loads(answers_path.read_text())
|
|
||||||
env_path = root_path / ".env"
|
|
||||||
env_content = env_path.read_text()
|
|
||||||
lines = []
|
|
||||||
for line in env_content.splitlines():
|
|
||||||
for key, value in answers.items():
|
|
||||||
upper_key = key.upper()
|
|
||||||
if line.startswith(f"{upper_key}="):
|
|
||||||
if " " in value:
|
|
||||||
content = f"{upper_key}={value!r}"
|
|
||||||
else:
|
|
||||||
content = f"{upper_key}={value}"
|
|
||||||
new_line = line.replace(line, content)
|
|
||||||
lines.append(new_line)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
lines.append(line)
|
|
||||||
env_path.write_text("\n".join(lines))
|
|
||||||
@@ -12,7 +12,6 @@ FRONTEND_HOST=https://makefire.fun
|
|||||||
ENVIRONMENT=production
|
ENVIRONMENT=production
|
||||||
|
|
||||||
PROJECT_NAME="Full Stack FastAPI Project"
|
PROJECT_NAME="Full Stack FastAPI Project"
|
||||||
STACK_NAME=full-stack-fastapi-project
|
|
||||||
|
|
||||||
# Backend
|
# Backend
|
||||||
BACKEND_CORS_ORIGINS="https://makefire.fun,https://api.makefire.fun"
|
BACKEND_CORS_ORIGINS="https://makefire.fun,https://api.makefire.fun"
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ jobs:
|
|||||||
FRONTEND_HOST=${{ secrets.FRONTEND_HOST }}
|
FRONTEND_HOST=${{ secrets.FRONTEND_HOST }}
|
||||||
ENVIRONMENT=production
|
ENVIRONMENT=production
|
||||||
PROJECT_NAME=${{ secrets.PROJECT_NAME }}
|
PROJECT_NAME=${{ secrets.PROJECT_NAME }}
|
||||||
STACK_NAME=${{ secrets.STACK_NAME }}
|
|
||||||
BACKEND_CORS_ORIGINS=${{ secrets.BACKEND_CORS_ORIGINS }}
|
BACKEND_CORS_ORIGINS=${{ secrets.BACKEND_CORS_ORIGINS }}
|
||||||
SECRET_KEY=${{ secrets.SECRET_KEY }}
|
SECRET_KEY=${{ secrets.SECRET_KEY }}
|
||||||
FIRST_SUPERUSER=${{ secrets.FIRST_SUPERUSER }}
|
FIRST_SUPERUSER=${{ secrets.FIRST_SUPERUSER }}
|
||||||
|
|||||||
65
README.md
65
README.md
@@ -22,8 +22,7 @@
|
|||||||
- 📫 Email based password recovery.
|
- 📫 Email based password recovery.
|
||||||
- 📬 [Mailcatcher](https://mailcatcher.me) for local email testing during development.
|
- 📬 [Mailcatcher](https://mailcatcher.me) for local email testing during development.
|
||||||
- ✅ Tests with [Pytest](https://pytest.org).
|
- ✅ Tests with [Pytest](https://pytest.org).
|
||||||
- 📞 [Traefik](https://traefik.io) as a reverse proxy / load balancer.
|
- 🚢 Deployment instructions using Docker Compose.
|
||||||
- 🚢 Deployment instructions using Docker Compose, including how to set up a frontend Traefik proxy to handle automatic HTTPS certificates.
|
|
||||||
- 🏭 CI (continuous integration) and CD (continuous deployment) based on GitHub Actions.
|
- 🏭 CI (continuous integration) and CD (continuous deployment) based on GitHub Actions.
|
||||||
|
|
||||||
### Dashboard Login
|
### Dashboard Login
|
||||||
@@ -146,66 +145,6 @@ python -c "import secrets; print(secrets.token_urlsafe(32))"
|
|||||||
|
|
||||||
Copy the content and use that as password / secret key. And run that again to generate another secure key.
|
Copy the content and use that as password / secret key. And run that again to generate another secure key.
|
||||||
|
|
||||||
## How To Use It - Alternative With Copier
|
|
||||||
|
|
||||||
This repository also supports generating a new project using [Copier](https://copier.readthedocs.io).
|
|
||||||
|
|
||||||
It will copy all the files, ask you configuration questions, and update the `.env` files with your answers.
|
|
||||||
|
|
||||||
### Install Copier
|
|
||||||
|
|
||||||
You can install Copier with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install copier
|
|
||||||
```
|
|
||||||
|
|
||||||
Or better, if you have [`pipx`](https://pipx.pypa.io/), you can run it with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pipx install copier
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note**: If you have `pipx`, installing copier is optional, you could run it directly.
|
|
||||||
|
|
||||||
### Generate a Project With Copier
|
|
||||||
|
|
||||||
Decide a name for your new project's directory, you will use it below. For example, `my-awesome-project`.
|
|
||||||
|
|
||||||
Go to the directory that will be the parent of your project, and run the command with your project's name:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
copier copy https://github.com/fastapi/full-stack-fastapi-template my-awesome-project --trust
|
|
||||||
```
|
|
||||||
|
|
||||||
If you have `pipx` and you didn't install `copier`, you can run it directly:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pipx run copier copy https://github.com/fastapi/full-stack-fastapi-template my-awesome-project --trust
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note** the `--trust` option is necessary to be able to execute a [post-creation script](https://github.com/fastapi/full-stack-fastapi-template/blob/master/.copier/update_dotenv.py) that updates your `.env` files.
|
|
||||||
|
|
||||||
### Input Variables
|
|
||||||
|
|
||||||
Copier will ask you for some data, you might want to have at hand before generating the project.
|
|
||||||
|
|
||||||
But don't worry, you can just update any of that in the `.env` files afterwards.
|
|
||||||
|
|
||||||
The input variables, with their default values (some auto generated) are:
|
|
||||||
|
|
||||||
- `project_name`: (default: `"FastAPI Project"`) The name of the project, shown to API users (in .env).
|
|
||||||
- `stack_name`: (default: `"fastapi-project"`) The name of the stack used for Docker Compose labels and project name (no spaces, no periods) (in .env).
|
|
||||||
- `secret_key`: (default: `"changethis"`) The secret key for the project, used for security, stored in .env, you can generate one with the method above.
|
|
||||||
- `first_superuser`: (default: `"admin@example.com"`) The email of the first superuser (in .env).
|
|
||||||
- `first_superuser_password`: (default: `"changethis"`) The password of the first superuser (in .env).
|
|
||||||
- `smtp_host`: (default: "") The SMTP server host to send emails, you can set it later in .env.
|
|
||||||
- `smtp_user`: (default: "") The SMTP server user to send emails, you can set it later in .env.
|
|
||||||
- `smtp_password`: (default: "") The SMTP server password to send emails, you can set it later in .env.
|
|
||||||
- `emails_from_email`: (default: `"info@example.com"`) The email account to send emails from, you can set it later in .env.
|
|
||||||
- `postgres_password`: (default: `"changethis"`) The password for the PostgreSQL database, stored in .env, you can generate one with the method above.
|
|
||||||
- `sentry_dsn`: (default: "") The DSN for Sentry, if you are using it, you can set it later in .env.
|
|
||||||
|
|
||||||
## Backend Development
|
## Backend Development
|
||||||
|
|
||||||
Backend docs: [backend/README.md](./backend/README.md).
|
Backend docs: [backend/README.md](./backend/README.md).
|
||||||
@@ -222,7 +161,7 @@ Deployment docs: [deployment.md](./deployment.md).
|
|||||||
|
|
||||||
General development docs: [development.md](./development.md).
|
General development docs: [development.md](./development.md).
|
||||||
|
|
||||||
This includes using Docker Compose, custom local domains, `.env` configurations, etc.
|
This includes using Docker Compose and `.env` configurations.
|
||||||
|
|
||||||
## Release Notes
|
## Release Notes
|
||||||
|
|
||||||
|
|||||||
@@ -1,50 +1,5 @@
|
|||||||
services:
|
services:
|
||||||
|
|
||||||
# Local services are available on their ports, but also available on:
|
|
||||||
# http://api.localhost.tiangolo.com: backend
|
|
||||||
# http://dashboard.localhost.tiangolo.com: frontend
|
|
||||||
# etc. To enable it, update .env, set:
|
|
||||||
# DOMAIN=localhost.tiangolo.com
|
|
||||||
proxy:
|
|
||||||
image: traefik:3.6
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
|
||||||
ports:
|
|
||||||
- "80:80"
|
|
||||||
- "8090:8080"
|
|
||||||
# Duplicate the command from compose.yml to add --api.insecure=true
|
|
||||||
command:
|
|
||||||
# Enable Docker in Traefik, so that it reads labels from Docker services
|
|
||||||
- --providers.docker
|
|
||||||
# Add a constraint to only use services with the label for this stack
|
|
||||||
- --providers.docker.constraints=Label(`traefik.constraint-label`, `traefik-public`)
|
|
||||||
# Do not expose all Docker services, only the ones explicitly exposed
|
|
||||||
- --providers.docker.exposedbydefault=false
|
|
||||||
# Create an entrypoint "http" listening on port 80
|
|
||||||
- --entrypoints.http.address=:80
|
|
||||||
# Create an entrypoint "https" listening on port 443
|
|
||||||
- --entrypoints.https.address=:443
|
|
||||||
# Enable the access log, with HTTP requests
|
|
||||||
- --accesslog
|
|
||||||
# Enable the Traefik log, for configurations and errors
|
|
||||||
- --log
|
|
||||||
# Enable debug logging for local development
|
|
||||||
- --log.level=DEBUG
|
|
||||||
# Enable the Dashboard and API
|
|
||||||
- --api
|
|
||||||
# Enable the Dashboard and API in insecure mode for local development
|
|
||||||
- --api.insecure=true
|
|
||||||
labels:
|
|
||||||
# Enable Traefik for this service, to make it available in the public network
|
|
||||||
- traefik.enable=true
|
|
||||||
- traefik.constraint-label=traefik-public
|
|
||||||
# Dummy https-redirect middleware that doesn't really redirect, only to
|
|
||||||
# allow running it locally
|
|
||||||
- traefik.http.middlewares.https-redirect.contenttype.autodetect=false
|
|
||||||
networks:
|
|
||||||
- traefik-public
|
|
||||||
- default
|
|
||||||
|
|
||||||
db:
|
db:
|
||||||
restart: "no"
|
restart: "no"
|
||||||
ports:
|
ports:
|
||||||
@@ -158,14 +113,9 @@ services:
|
|||||||
- MAILCATCHER_HOST=http://mailcatcher:1080
|
- MAILCATCHER_HOST=http://mailcatcher:1080
|
||||||
# For the reports when run locally
|
# For the reports when run locally
|
||||||
- PLAYWRIGHT_HTML_HOST=0.0.0.0
|
- PLAYWRIGHT_HTML_HOST=0.0.0.0
|
||||||
- CI=${CI}
|
- CI=${CI:-false}
|
||||||
volumes:
|
volumes:
|
||||||
- ./frontend/blob-report:/app/frontend/blob-report
|
- ./frontend/blob-report:/app/frontend/blob-report
|
||||||
- ./frontend/test-results:/app/frontend/test-results
|
- ./frontend/test-results:/app/frontend/test-results
|
||||||
ports:
|
ports:
|
||||||
- 9323:9323
|
- 9323:9323
|
||||||
|
|
||||||
networks:
|
|
||||||
traefik-public:
|
|
||||||
# For local dev, don't expect an external Traefik network
|
|
||||||
external: false
|
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
services:
|
|
||||||
traefik:
|
|
||||||
image: traefik:3.6
|
|
||||||
ports:
|
|
||||||
# Listen on port 80, default for HTTP, necessary to redirect to HTTPS
|
|
||||||
- 80:80
|
|
||||||
# Listen on port 443, default for HTTPS
|
|
||||||
- 443:443
|
|
||||||
restart: always
|
|
||||||
labels:
|
|
||||||
# Enable Traefik for this service, to make it available in the public network
|
|
||||||
- traefik.enable=true
|
|
||||||
# Use the traefik-public network (declared below)
|
|
||||||
- traefik.docker.network=traefik-public
|
|
||||||
# Define the port inside of the Docker service to use
|
|
||||||
- traefik.http.services.traefik-dashboard.loadbalancer.server.port=8080
|
|
||||||
# Make Traefik use this domain (from an environment variable) in HTTP
|
|
||||||
- traefik.http.routers.traefik-dashboard-http.entrypoints=http
|
|
||||||
- traefik.http.routers.traefik-dashboard-http.rule=Host(`traefik.${DOMAIN?Variable not set}`)
|
|
||||||
# traefik-https the actual router using HTTPS
|
|
||||||
- traefik.http.routers.traefik-dashboard-https.entrypoints=https
|
|
||||||
- traefik.http.routers.traefik-dashboard-https.rule=Host(`traefik.${DOMAIN?Variable not set}`)
|
|
||||||
- traefik.http.routers.traefik-dashboard-https.tls=true
|
|
||||||
# Use the "le" (Let's Encrypt) resolver created below
|
|
||||||
- traefik.http.routers.traefik-dashboard-https.tls.certresolver=le
|
|
||||||
# Use the special Traefik service api@internal with the web UI/Dashboard
|
|
||||||
- traefik.http.routers.traefik-dashboard-https.service=api@internal
|
|
||||||
# https-redirect middleware to redirect HTTP to HTTPS
|
|
||||||
- traefik.http.middlewares.https-redirect.redirectscheme.scheme=https
|
|
||||||
- traefik.http.middlewares.https-redirect.redirectscheme.permanent=true
|
|
||||||
# traefik-http set up only to use the middleware to redirect to https
|
|
||||||
- traefik.http.routers.traefik-dashboard-http.middlewares=https-redirect
|
|
||||||
# admin-auth middleware with HTTP Basic auth
|
|
||||||
# Using the environment variables USERNAME and HASHED_PASSWORD
|
|
||||||
- traefik.http.middlewares.admin-auth.basicauth.users=${USERNAME?Variable not set}:${HASHED_PASSWORD?Variable not set}
|
|
||||||
# Enable HTTP Basic auth, using the middleware created above
|
|
||||||
- traefik.http.routers.traefik-dashboard-https.middlewares=admin-auth
|
|
||||||
volumes:
|
|
||||||
# Add Docker as a mounted volume, so that Traefik can read the labels of other services
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
||||||
# Mount the volume to store the certificates
|
|
||||||
- traefik-public-certificates:/certificates
|
|
||||||
command:
|
|
||||||
# Enable Docker in Traefik, so that it reads labels from Docker services
|
|
||||||
- --providers.docker
|
|
||||||
# Do not expose all Docker services, only the ones explicitly exposed
|
|
||||||
- --providers.docker.exposedbydefault=false
|
|
||||||
# Create an entrypoint "http" listening on port 80
|
|
||||||
- --entrypoints.http.address=:80
|
|
||||||
# Create an entrypoint "https" listening on port 443
|
|
||||||
- --entrypoints.https.address=:443
|
|
||||||
# Create the certificate resolver "le" for Let's Encrypt, uses the environment variable EMAIL
|
|
||||||
- --certificatesresolvers.le.acme.email=${EMAIL?Variable not set}
|
|
||||||
# Store the Let's Encrypt certificates in the mounted volume
|
|
||||||
- --certificatesresolvers.le.acme.storage=/certificates/acme.json
|
|
||||||
# Use the TLS Challenge for Let's Encrypt
|
|
||||||
- --certificatesresolvers.le.acme.tlschallenge=true
|
|
||||||
# Enable the access log, with HTTP requests
|
|
||||||
- --accesslog
|
|
||||||
# Enable the Traefik log, for configurations and errors
|
|
||||||
- --log
|
|
||||||
# Enable the Dashboard and API
|
|
||||||
- --api
|
|
||||||
networks:
|
|
||||||
# Use the public network created to be shared between Traefik and
|
|
||||||
# any other service that needs to be publicly available with HTTPS
|
|
||||||
- traefik-public
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
# Create a volume to store the certificates, even if the container is recreated
|
|
||||||
traefik-public-certificates:
|
|
||||||
|
|
||||||
networks:
|
|
||||||
# Use the previously created public network "traefik-public", shared with other
|
|
||||||
# services that need to be publicly available via this Traefik
|
|
||||||
traefik-public:
|
|
||||||
external: true
|
|
||||||
63
compose.yml
63
compose.yml
@@ -22,34 +22,16 @@ services:
|
|||||||
adminer:
|
adminer:
|
||||||
image: adminer
|
image: adminer
|
||||||
restart: always
|
restart: always
|
||||||
networks:
|
|
||||||
- traefik-public
|
|
||||||
- default
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
environment:
|
environment:
|
||||||
- ADMINER_DESIGN=pepa-linha-dark
|
- ADMINER_DESIGN=pepa-linha-dark
|
||||||
labels:
|
|
||||||
- traefik.enable=true
|
|
||||||
- traefik.docker.network=traefik-public
|
|
||||||
- traefik.constraint-label=traefik-public
|
|
||||||
- traefik.http.routers.${STACK_NAME?Variable not set}-adminer-http.rule=Host(`adminer.${DOMAIN?Variable not set}`)
|
|
||||||
- traefik.http.routers.${STACK_NAME?Variable not set}-adminer-http.entrypoints=http
|
|
||||||
- traefik.http.routers.${STACK_NAME?Variable not set}-adminer-http.middlewares=https-redirect
|
|
||||||
- traefik.http.routers.${STACK_NAME?Variable not set}-adminer-https.rule=Host(`adminer.${DOMAIN?Variable not set}`)
|
|
||||||
- traefik.http.routers.${STACK_NAME?Variable not set}-adminer-https.entrypoints=https
|
|
||||||
- traefik.http.routers.${STACK_NAME?Variable not set}-adminer-https.tls=true
|
|
||||||
- traefik.http.routers.${STACK_NAME?Variable not set}-adminer-https.tls.certresolver=le
|
|
||||||
- traefik.http.services.${STACK_NAME?Variable not set}-adminer.loadbalancer.server.port=8080
|
|
||||||
|
|
||||||
prestart:
|
prestart:
|
||||||
image: '${DOCKER_IMAGE_BACKEND?Variable not set}:${TAG-latest}'
|
image: '${DOCKER_IMAGE_BACKEND?Variable not set}:${TAG-latest}'
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: backend/Dockerfile
|
dockerfile: backend/Dockerfile
|
||||||
networks:
|
|
||||||
- traefik-public
|
|
||||||
- default
|
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
@@ -79,9 +61,6 @@ services:
|
|||||||
backend:
|
backend:
|
||||||
image: '${DOCKER_IMAGE_BACKEND?Variable not set}:${TAG-latest}'
|
image: '${DOCKER_IMAGE_BACKEND?Variable not set}:${TAG-latest}'
|
||||||
restart: always
|
restart: always
|
||||||
networks:
|
|
||||||
- traefik-public
|
|
||||||
- default
|
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
@@ -118,23 +97,6 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: backend/Dockerfile
|
dockerfile: backend/Dockerfile
|
||||||
labels:
|
|
||||||
- traefik.enable=true
|
|
||||||
- traefik.docker.network=traefik-public
|
|
||||||
- traefik.constraint-label=traefik-public
|
|
||||||
|
|
||||||
- traefik.http.services.${STACK_NAME?Variable not set}-backend.loadbalancer.server.port=8000
|
|
||||||
|
|
||||||
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=Host(`api.${DOMAIN?Variable not set}`)
|
|
||||||
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.entrypoints=http
|
|
||||||
|
|
||||||
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.rule=Host(`api.${DOMAIN?Variable not set}`)
|
|
||||||
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.entrypoints=https
|
|
||||||
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.tls=true
|
|
||||||
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.tls.certresolver=le
|
|
||||||
|
|
||||||
# Enable redirection for HTTP and HTTPS
|
|
||||||
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.middlewares=https-redirect
|
|
||||||
|
|
||||||
mqtt-ingestor:
|
mqtt-ingestor:
|
||||||
image: '${DOCKER_IMAGE_BACKEND?Variable not set}:${TAG-latest}'
|
image: '${DOCKER_IMAGE_BACKEND?Variable not set}:${TAG-latest}'
|
||||||
@@ -216,37 +178,12 @@ services:
|
|||||||
frontend:
|
frontend:
|
||||||
image: '${DOCKER_IMAGE_FRONTEND?Variable not set}:${TAG-latest}'
|
image: '${DOCKER_IMAGE_FRONTEND?Variable not set}:${TAG-latest}'
|
||||||
restart: always
|
restart: always
|
||||||
networks:
|
|
||||||
- traefik-public
|
|
||||||
- default
|
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: frontend/Dockerfile
|
dockerfile: frontend/Dockerfile
|
||||||
args:
|
args:
|
||||||
- VITE_API_URL=https://api.${DOMAIN?Variable not set}
|
- VITE_API_URL=https://api.${DOMAIN?Variable not set}
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
labels:
|
|
||||||
- traefik.enable=true
|
|
||||||
- traefik.docker.network=traefik-public
|
|
||||||
- traefik.constraint-label=traefik-public
|
|
||||||
|
|
||||||
- traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80
|
|
||||||
|
|
||||||
- traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=Host(`dashboard.${DOMAIN?Variable not set}`)
|
|
||||||
- traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.entrypoints=http
|
|
||||||
|
|
||||||
- traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.rule=Host(`dashboard.${DOMAIN?Variable not set}`)
|
|
||||||
- traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.entrypoints=https
|
|
||||||
- traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.tls=true
|
|
||||||
- traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.tls.certresolver=le
|
|
||||||
|
|
||||||
# Enable redirection for HTTP and HTTPS
|
|
||||||
- traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.middlewares=https-redirect
|
|
||||||
volumes:
|
volumes:
|
||||||
app-db-data:
|
app-db-data:
|
||||||
mqtt-archive-data:
|
mqtt-archive-data:
|
||||||
|
|
||||||
networks:
|
|
||||||
traefik-public:
|
|
||||||
# Allow setting it to false for testing
|
|
||||||
external: true
|
|
||||||
|
|||||||
100
copier.yml
100
copier.yml
@@ -1,100 +0,0 @@
|
|||||||
project_name:
|
|
||||||
type: str
|
|
||||||
help: The name of the project, shown to API users (in .env)
|
|
||||||
default: FastAPI Project
|
|
||||||
|
|
||||||
stack_name:
|
|
||||||
type: str
|
|
||||||
help: The name of the stack used for Docker Compose labels (no spaces) (in .env)
|
|
||||||
default: fastapi-project
|
|
||||||
|
|
||||||
secret_key:
|
|
||||||
type: str
|
|
||||||
help: |
|
|
||||||
'The secret key for the project, used for security,
|
|
||||||
stored in .env, you can generate one with:
|
|
||||||
python -c "import secrets; print(secrets.token_urlsafe(32))"'
|
|
||||||
default: changethis
|
|
||||||
|
|
||||||
first_superuser:
|
|
||||||
type: str
|
|
||||||
help: The email of the first superuser (in .env)
|
|
||||||
default: admin@example.com
|
|
||||||
|
|
||||||
first_superuser_password:
|
|
||||||
type: str
|
|
||||||
help: The password of the first superuser (in .env)
|
|
||||||
default: changethis
|
|
||||||
|
|
||||||
smtp_host:
|
|
||||||
type: str
|
|
||||||
help: The SMTP server host to send emails, you can set it later in .env
|
|
||||||
default: ""
|
|
||||||
|
|
||||||
smtp_user:
|
|
||||||
type: str
|
|
||||||
help: The SMTP server user to send emails, you can set it later in .env
|
|
||||||
default: ""
|
|
||||||
|
|
||||||
smtp_password:
|
|
||||||
type: str
|
|
||||||
help: The SMTP server password to send emails, you can set it later in .env
|
|
||||||
default: ""
|
|
||||||
|
|
||||||
emails_from_email:
|
|
||||||
type: str
|
|
||||||
help: The email account to send emails from, you can set it later in .env
|
|
||||||
default: info@example.com
|
|
||||||
|
|
||||||
postgres_password:
|
|
||||||
type: str
|
|
||||||
help: |
|
|
||||||
'The password for the PostgreSQL database, stored in .env,
|
|
||||||
you can generate one with:
|
|
||||||
python -c "import secrets; print(secrets.token_urlsafe(32))"'
|
|
||||||
default: changethis
|
|
||||||
|
|
||||||
sentry_dsn:
|
|
||||||
type: str
|
|
||||||
help: The DSN for Sentry, if you are using it, you can set it later in .env
|
|
||||||
default: ""
|
|
||||||
|
|
||||||
_exclude:
|
|
||||||
# Global
|
|
||||||
- .vscode
|
|
||||||
- .mypy_cache
|
|
||||||
# Python
|
|
||||||
- __pycache__
|
|
||||||
- app.egg-info
|
|
||||||
- "*.pyc"
|
|
||||||
- .mypy_cache
|
|
||||||
- .coverage
|
|
||||||
- htmlcov
|
|
||||||
- .cache
|
|
||||||
- .venv
|
|
||||||
# Frontend
|
|
||||||
# Logs
|
|
||||||
- logs
|
|
||||||
- "*.log"
|
|
||||||
- npm-debug.log*
|
|
||||||
- yarn-debug.log*
|
|
||||||
- yarn-error.log*
|
|
||||||
- pnpm-debug.log*
|
|
||||||
- lerna-debug.log*
|
|
||||||
- node_modules
|
|
||||||
- dist
|
|
||||||
- dist-ssr
|
|
||||||
- "*.local"
|
|
||||||
# Editor directories and files
|
|
||||||
- .idea
|
|
||||||
- .DS_Store
|
|
||||||
- "*.suo"
|
|
||||||
- "*.ntvs*"
|
|
||||||
- "*.njsproj"
|
|
||||||
- "*.sln"
|
|
||||||
- "*.sw?"
|
|
||||||
|
|
||||||
_answers_file: .copier/.copier-answers.yml
|
|
||||||
|
|
||||||
_tasks:
|
|
||||||
- ["{{ _copier_python }}", .copier/update_dotenv.py]
|
|
||||||
@@ -33,7 +33,7 @@ OpenResty (1Panel 管理, SSL, 端口 80/443)
|
|||||||
**关键设计决策:**
|
**关键设计决策:**
|
||||||
- 后端和前端容器只绑定 `127.0.0.1`,不对外暴露,由 OpenResty 统一反代
|
- 后端和前端容器只绑定 `127.0.0.1`,不对外暴露,由 OpenResty 统一反代
|
||||||
- 所有容器加入 `1panel-network`,可直接通过 `postgresql` 主机名访问已有数据库
|
- 所有容器加入 `1panel-network`,可直接通过 `postgresql` 主机名访问已有数据库
|
||||||
- 不使用 Traefik(用 1Panel 自带的 OpenResty 替代)
|
- 使用 1Panel 自带的 OpenResty 作为反向代理
|
||||||
- 不启动独立 PostgreSQL 容器(复用已有的)
|
- 不启动独立 PostgreSQL 容器(复用已有的)
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -324,7 +324,6 @@ sudo systemctl status gitea-runner
|
|||||||
| `DOMAIN` | `makefire.fun` |
|
| `DOMAIN` | `makefire.fun` |
|
||||||
| `FRONTEND_HOST` | `https://makefire.fun` |
|
| `FRONTEND_HOST` | `https://makefire.fun` |
|
||||||
| `PROJECT_NAME` | `SpatialHub` |
|
| `PROJECT_NAME` | `SpatialHub` |
|
||||||
| `STACK_NAME` | `spatialhub` |
|
|
||||||
| `BACKEND_CORS_ORIGINS` | `https://makefire.fun,https://api.makefire.fun` |
|
| `BACKEND_CORS_ORIGINS` | `https://makefire.fun,https://api.makefire.fun` |
|
||||||
| `SECRET_KEY` | *(用 `openssl rand -hex 32` 生成)* |
|
| `SECRET_KEY` | *(用 `openssl rand -hex 32` 生成)* |
|
||||||
| `FIRST_SUPERUSER` | `admin@makefire.fun` |
|
| `FIRST_SUPERUSER` | `admin@makefire.fun` |
|
||||||
|
|||||||
387
deployment.md
387
deployment.md
@@ -1,344 +1,87 @@
|
|||||||
# FastAPI Project - Deployment
|
# FastAPI Project - Deployment
|
||||||
|
|
||||||
You can deploy the project using Docker Compose to a remote server.
|
You can deploy this project with Docker Compose and put it behind an external reverse proxy (for example OpenResty/Nginx).
|
||||||
|
|
||||||
This project expects you to have a Traefik proxy handling communication to the outside world and HTTPS certificates.
|
This repository uses an external reverse proxy setup and keeps Compose deployment focused on app services.
|
||||||
|
|
||||||
You can use CI/CD (continuous integration and continuous deployment) systems to deploy automatically, there are already configurations to do it with GitHub Actions.
|
|
||||||
|
|
||||||
But you have to configure a couple things first. 🤓
|
|
||||||
|
|
||||||
## Preparation
|
## Preparation
|
||||||
|
|
||||||
* Have a remote server ready and available.
|
* Have a remote server ready.
|
||||||
* Configure the DNS records of your domain to point to the IP of the server you just created.
|
* Configure DNS records to point your domain to that server.
|
||||||
* Configure a wildcard subdomain for your domain, so that you can have multiple subdomains for different services, e.g. `*.fastapi-project.example.com`. This will be useful for accessing different components, like `dashboard.fastapi-project.example.com`, `api.fastapi-project.example.com`, `traefik.fastapi-project.example.com`, `adminer.fastapi-project.example.com`, etc. And also for `staging`, like `dashboard.staging.fastapi-project.example.com`, `adminer.staging.fastapi-project.example.com`, etc.
|
* Install Docker Engine on the server.
|
||||||
* Install and configure [Docker](https://docs.docker.com/engine/install/) on the remote server (Docker Engine, not Docker Desktop).
|
* Configure OpenResty (or your reverse proxy) to forward traffic:
|
||||||
|
* frontend domain/path -> frontend container port
|
||||||
## Public Traefik
|
* API domain/path -> backend container port
|
||||||
|
|
||||||
We need a Traefik proxy to handle incoming connections and HTTPS certificates.
|
|
||||||
|
|
||||||
You need to do these next steps only once.
|
|
||||||
|
|
||||||
### Traefik Docker Compose
|
|
||||||
|
|
||||||
* Create a remote directory to store your Traefik Docker Compose file:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p /root/code/traefik-public/
|
|
||||||
```
|
|
||||||
|
|
||||||
Copy the Traefik Docker Compose file to your server. You could do it by running the command `rsync` in your local terminal:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rsync -a compose.traefik.yml root@your-server.example.com:/root/code/traefik-public/
|
|
||||||
```
|
|
||||||
|
|
||||||
### Traefik Public Network
|
|
||||||
|
|
||||||
This Traefik will expect a Docker "public network" named `traefik-public` to communicate with your stack(s).
|
|
||||||
|
|
||||||
This way, there will be a single public Traefik proxy that handles the communication (HTTP and HTTPS) with the outside world, and then behind that, you could have one or more stacks with different domains, even if they are on the same single server.
|
|
||||||
|
|
||||||
To create a Docker "public network" named `traefik-public` run the following command in your remote server:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker network create traefik-public
|
|
||||||
```
|
|
||||||
|
|
||||||
### Traefik Environment Variables
|
|
||||||
|
|
||||||
The Traefik Docker Compose file expects some environment variables to be set in your terminal before starting it. You can do it by running the following commands in your remote server.
|
|
||||||
|
|
||||||
* Create the username for HTTP Basic Auth, e.g.:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export USERNAME=admin
|
|
||||||
```
|
|
||||||
|
|
||||||
* Create an environment variable with the password for HTTP Basic Auth, e.g.:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export PASSWORD=changethis
|
|
||||||
```
|
|
||||||
|
|
||||||
* Use openssl to generate the "hashed" version of the password for HTTP Basic Auth and store it in an environment variable:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export HASHED_PASSWORD=$(openssl passwd -apr1 $PASSWORD)
|
|
||||||
```
|
|
||||||
|
|
||||||
To verify that the hashed password is correct, you can print it:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
echo $HASHED_PASSWORD
|
|
||||||
```
|
|
||||||
|
|
||||||
* Create an environment variable with the domain name for your server, e.g.:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export DOMAIN=fastapi-project.example.com
|
|
||||||
```
|
|
||||||
|
|
||||||
* Create an environment variable with the email for Let's Encrypt, e.g.:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export EMAIL=admin@example.com
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note**: you need to set a different email, an email `@example.com` won't work.
|
|
||||||
|
|
||||||
### Start the Traefik Docker Compose
|
|
||||||
|
|
||||||
Go to the directory where you copied the Traefik Docker Compose file in your remote server:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /root/code/traefik-public/
|
|
||||||
```
|
|
||||||
|
|
||||||
Now with the environment variables set and the `compose.traefik.yml` in place, you can start the Traefik Docker Compose running the following command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose -f compose.traefik.yml up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
## Deploy the FastAPI Project
|
|
||||||
|
|
||||||
Now that you have Traefik in place you can deploy your FastAPI project with Docker Compose.
|
|
||||||
|
|
||||||
**Note**: You might want to jump ahead to the section about Continuous Deployment with GitHub Actions.
|
|
||||||
|
|
||||||
## Copy the Code
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rsync -av --filter=":- .gitignore" ./ root@your-server.example.com:/root/code/app/
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: `--filter=":- .gitignore"` tells `rsync` to use the same rules as git, ignore files ignored by git, like the Python virtual environment.
|
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
You need to set some environment variables first.
|
Create `.env.production` based on `.env.production.example` and fill in real values.
|
||||||
|
|
||||||
### Generate secret keys
|
Minimum required values:
|
||||||
|
|
||||||
Some environment variables in the `.env` file have a default value of `changethis`.
|
* `DOMAIN`
|
||||||
|
* `FRONTEND_HOST`
|
||||||
You have to change them with a secret key, to generate secret keys you can run the following command:
|
* `ENVIRONMENT=production`
|
||||||
|
* `SECRET_KEY`
|
||||||
```bash
|
|
||||||
python -c "import secrets; print(secrets.token_urlsafe(32))"
|
|
||||||
```
|
|
||||||
|
|
||||||
Copy the content and use that as password / secret key. And run that again to generate another secure key.
|
|
||||||
|
|
||||||
### Required Environment Variables
|
|
||||||
|
|
||||||
Set the `ENVIRONMENT`, by default `local` (for development), but when deploying to a server you would put something like `staging` or `production`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export ENVIRONMENT=production
|
|
||||||
```
|
|
||||||
|
|
||||||
Set the `DOMAIN`, by default `localhost` (for development), but when deploying you would use your own domain, for example:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export DOMAIN=fastapi-project.example.com
|
|
||||||
```
|
|
||||||
|
|
||||||
Set the `POSTGRES_PASSWORD` to something different than `changethis`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export POSTGRES_PASSWORD="changethis"
|
|
||||||
```
|
|
||||||
|
|
||||||
Set the `SECRET_KEY`, used to sign tokens:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export SECRET_KEY="changethis"
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: you can use the Python command above to generate a secure secret key.
|
|
||||||
|
|
||||||
Set the `FIRST_SUPER_USER_PASSWORD` to something different than `changethis`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export FIRST_SUPERUSER_PASSWORD="changethis"
|
|
||||||
```
|
|
||||||
|
|
||||||
Set the `BACKEND_CORS_ORIGINS` to include your domain:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export BACKEND_CORS_ORIGINS="https://dashboard.${DOMAIN?Variable not set},https://api.${DOMAIN?Variable not set}"
|
|
||||||
```
|
|
||||||
|
|
||||||
You can set several other environment variables:
|
|
||||||
|
|
||||||
* `PROJECT_NAME`: The name of the project, used in the API for the docs and emails.
|
|
||||||
* `STACK_NAME`: The name of the stack used for Docker Compose labels and project name, this should be different for `staging`, `production`, etc. You could use the same domain replacing dots with dashes, e.g. `fastapi-project-example-com` and `staging-fastapi-project-example-com`.
|
|
||||||
* `BACKEND_CORS_ORIGINS`: A list of allowed CORS origins separated by commas.
|
|
||||||
* `FIRST_SUPERUSER`: The email of the first superuser, this superuser will be the one that can create new users.
|
|
||||||
* `SMTP_HOST`: The SMTP server host to send emails, this would come from your email provider (E.g. Mailgun, Sparkpost, Sendgrid, etc).
|
|
||||||
* `SMTP_USER`: The SMTP server user to send emails.
|
|
||||||
* `SMTP_PASSWORD`: The SMTP server password to send emails.
|
|
||||||
* `EMAILS_FROM_EMAIL`: The email account to send emails from.
|
|
||||||
* `POSTGRES_SERVER`: The hostname of the PostgreSQL server. You can leave the default of `db`, provided by the same Docker Compose. You normally wouldn't need to change this unless you are using a third-party provider.
|
|
||||||
* `POSTGRES_PORT`: The port of the PostgreSQL server. You can leave the default. You normally wouldn't need to change this unless you are using a third-party provider.
|
|
||||||
* `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.
|
|
||||||
|
|
||||||
## GitHub Actions Environment Variables
|
|
||||||
|
|
||||||
There are some environment variables only used by GitHub Actions that you can configure:
|
|
||||||
|
|
||||||
* `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.
|
|
||||||
* `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.
|
|
||||||
|
|
||||||
### Deploy with Docker Compose
|
|
||||||
|
|
||||||
With the environment variables in place, you can deploy with Docker Compose:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /root/code/app/
|
|
||||||
docker compose -f compose.yml build
|
|
||||||
docker compose -f compose.yml up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
For production you wouldn't want to have the overrides in `compose.override.yml`, that's why we explicitly specify `compose.yml` as the file to use.
|
|
||||||
|
|
||||||
## Continuous Deployment (CD)
|
|
||||||
|
|
||||||
You can use GitHub Actions to deploy your project automatically. 😎
|
|
||||||
|
|
||||||
You can have multiple environment deployments.
|
|
||||||
|
|
||||||
There are already two environments configured, `staging` and `production`. 🚀
|
|
||||||
|
|
||||||
### Install GitHub Actions Runner
|
|
||||||
|
|
||||||
* On your remote server, create a user for your GitHub Actions:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo adduser github
|
|
||||||
```
|
|
||||||
|
|
||||||
* Add Docker permissions to the `github` user:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo usermod -aG docker github
|
|
||||||
```
|
|
||||||
|
|
||||||
* Temporarily switch to the `github` user:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo su - github
|
|
||||||
```
|
|
||||||
|
|
||||||
* Go to the `github` user's home directory:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
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).
|
|
||||||
|
|
||||||
* 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.
|
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
exit
|
|
||||||
```
|
|
||||||
|
|
||||||
After you do it, you will be on the previous user again. And you will be on the previous directory, belonging to that user.
|
|
||||||
|
|
||||||
Before being able to go the `github` user directory, you need to become the `root` user (you might already be):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo su
|
|
||||||
```
|
|
||||||
|
|
||||||
* As the `root` user, go to the `actions-runner` directory inside of the `github` user's home directory:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/github/actions-runner
|
|
||||||
```
|
|
||||||
|
|
||||||
* Install the self-hosted runner as a service with the user `github`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./svc.sh install github
|
|
||||||
```
|
|
||||||
|
|
||||||
* Start the service:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./svc.sh start
|
|
||||||
```
|
|
||||||
|
|
||||||
* Check the status of the service:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./svc.sh status
|
|
||||||
```
|
|
||||||
|
|
||||||
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).
|
|
||||||
|
|
||||||
### Set Secrets
|
|
||||||
|
|
||||||
On your repository, configure secrets for the environment variables you need, the same ones described above, including `SECRET_KEY`, etc. Follow the [official GitHub guide for setting repository secrets](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository).
|
|
||||||
|
|
||||||
The current Github Actions workflows expect these secrets:
|
|
||||||
|
|
||||||
* `DOMAIN_PRODUCTION`
|
|
||||||
* `DOMAIN_STAGING`
|
|
||||||
* `STACK_NAME_PRODUCTION`
|
|
||||||
* `STACK_NAME_STAGING`
|
|
||||||
* `EMAILS_FROM_EMAIL`
|
|
||||||
* `FIRST_SUPERUSER`
|
* `FIRST_SUPERUSER`
|
||||||
* `FIRST_SUPERUSER_PASSWORD`
|
* `FIRST_SUPERUSER_PASSWORD`
|
||||||
|
* `POSTGRES_SERVER`
|
||||||
|
* `POSTGRES_PORT`
|
||||||
|
* `POSTGRES_DB`
|
||||||
|
* `POSTGRES_USER`
|
||||||
* `POSTGRES_PASSWORD`
|
* `POSTGRES_PASSWORD`
|
||||||
* `SECRET_KEY`
|
|
||||||
* `LATEST_CHANGES`
|
|
||||||
* `SMOKESHOW_AUTH_KEY`
|
|
||||||
|
|
||||||
## GitHub Action Deployment Workflows
|
Optional values:
|
||||||
|
|
||||||
There are GitHub Action workflows in the `.github/workflows` directory already configured for deploying to the environments (GitHub Actions runners with the labels):
|
* `SMTP_*` and `EMAILS_FROM_EMAIL`
|
||||||
|
* `SENTRY_DSN`
|
||||||
|
* `MQTT_*` pipeline settings
|
||||||
|
* `DOCKER_IMAGE_BACKEND`
|
||||||
|
* `DOCKER_IMAGE_FRONTEND`
|
||||||
|
|
||||||
* `staging`: after pushing (or merging) to the branch `master`.
|
## Deploy with Docker Compose
|
||||||
* `production`: after publishing a release.
|
|
||||||
|
|
||||||
If you need to add extra environments you could use those as a starting point.
|
```bash
|
||||||
|
cd /path/to/project
|
||||||
|
|
||||||
|
docker compose --env-file .env.production -f compose.prod.yml build
|
||||||
|
|
||||||
|
docker compose --env-file .env.production -f compose.prod.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
To update after code changes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose --env-file .env.production -f compose.prod.yml down --remove-orphans
|
||||||
|
docker compose --env-file .env.production -f compose.prod.yml up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verify
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose --env-file .env.production -f compose.prod.yml ps
|
||||||
|
docker compose --env-file .env.production -f compose.prod.yml logs backend --tail 100
|
||||||
|
docker compose --env-file .env.production -f compose.prod.yml logs frontend --tail 100
|
||||||
|
```
|
||||||
|
|
||||||
|
Backend health check endpoint:
|
||||||
|
|
||||||
|
* `http://<backend-host>:18000/api/v1/utils/health-check/` (or your internal mapped port)
|
||||||
|
|
||||||
|
## CI/CD
|
||||||
|
|
||||||
|
The Gitea workflow at `.gitea/workflows/deploy.yml` deploys using:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose --env-file .env.production -f compose.prod.yml ...
|
||||||
|
```
|
||||||
|
|
||||||
|
So production deploys are isolated from local development overrides.
|
||||||
|
|
||||||
## URLs
|
## URLs
|
||||||
|
|
||||||
Replace `fastapi-project.example.com` with your domain.
|
Replace with your real domains configured in OpenResty:
|
||||||
|
|
||||||
### Main Traefik Dashboard
|
* Frontend: `https://<your-frontend-domain>`
|
||||||
|
* Backend API docs: `https://<your-api-domain>/docs`
|
||||||
Traefik UI: `https://traefik.fastapi-project.example.com`
|
* Backend API base URL: `https://<your-api-domain>`
|
||||||
|
|
||||||
### Production
|
|
||||||
|
|
||||||
Frontend: `https://dashboard.fastapi-project.example.com`
|
|
||||||
|
|
||||||
Backend API docs: `https://api.fastapi-project.example.com/docs`
|
|
||||||
|
|
||||||
Backend API base URL: `https://api.fastapi-project.example.com`
|
|
||||||
|
|
||||||
Adminer: `https://adminer.fastapi-project.example.com`
|
|
||||||
|
|
||||||
### Staging
|
|
||||||
|
|
||||||
Frontend: `https://dashboard.staging.fastapi-project.example.com`
|
|
||||||
|
|
||||||
Backend API docs: `https://api.staging.fastapi-project.example.com/docs`
|
|
||||||
|
|
||||||
Backend API base URL: `https://api.staging.fastapi-project.example.com`
|
|
||||||
|
|
||||||
Adminer: `https://adminer.staging.fastapi-project.example.com`
|
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ Automatic interactive documentation with Swagger UI (from the OpenAPI backend):
|
|||||||
|
|
||||||
Adminer, database web administration: <http://localhost:8080>
|
Adminer, database web administration: <http://localhost:8080>
|
||||||
|
|
||||||
Traefik UI, to see how the routes are being handled by the proxy: <http://localhost:8090>
|
|
||||||
|
|
||||||
**Note**: The first time you start your stack, it might take a minute for it to be ready. While the backend waits for the database to be ready and configures everything. You can check the logs to monitor it.
|
**Note**: The first time you start your stack, it might take a minute for it to be ready. While the backend waits for the database to be ready and configures everything. You can check the logs to monitor it.
|
||||||
|
|
||||||
To check the logs, run (in another terminal):
|
To check the logs, run (in another terminal):
|
||||||
@@ -79,34 +77,6 @@ cd backend
|
|||||||
fastapi dev app/main.py
|
fastapi dev app/main.py
|
||||||
```
|
```
|
||||||
|
|
||||||
## Docker Compose in `localhost.tiangolo.com`
|
|
||||||
|
|
||||||
When you start the Docker Compose stack, it uses `localhost` by default, with different ports for each service (backend, frontend, adminer, etc).
|
|
||||||
|
|
||||||
When you deploy it to production (or staging), it will deploy each service in a different subdomain, like `api.example.com` for the backend and `dashboard.example.com` for the frontend.
|
|
||||||
|
|
||||||
In the guide about [deployment](deployment.md) you can read about Traefik, the configured proxy. That's the component in charge of transmitting traffic to each service based on the subdomain.
|
|
||||||
|
|
||||||
If you want to test that it's all working locally, you can edit the local `.env` file, and change:
|
|
||||||
|
|
||||||
```dotenv
|
|
||||||
DOMAIN=localhost.tiangolo.com
|
|
||||||
```
|
|
||||||
|
|
||||||
That will be used by the Docker Compose files to configure the base domain for the services.
|
|
||||||
|
|
||||||
Traefik will use this to transmit traffic at `api.localhost.tiangolo.com` to the backend, and traffic at `dashboard.localhost.tiangolo.com` to the frontend.
|
|
||||||
|
|
||||||
The domain `localhost.tiangolo.com` is a special domain that is configured (with all its subdomains) to point to `127.0.0.1`. This way you can use that for your local development.
|
|
||||||
|
|
||||||
After you update it, run again:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose watch
|
|
||||||
```
|
|
||||||
|
|
||||||
When deploying, for example in production, the main Traefik is configured outside of the Docker Compose files. For local development, there's an included Traefik in `compose.override.yml`, just to let you test that the domains work as expected, for example with `api.localhost.tiangolo.com` and `dashboard.localhost.tiangolo.com`.
|
|
||||||
|
|
||||||
## Docker Compose files and env vars
|
## Docker Compose files and env vars
|
||||||
|
|
||||||
There is a main `compose.yml` file with all the configurations that apply to the whole stack, it is used automatically by `docker compose`.
|
There is a main `compose.yml` file with all the configurations that apply to the whole stack, it is used automatically by `docker compose`.
|
||||||
@@ -198,24 +168,4 @@ Automatic Alternative Docs (ReDoc): <http://localhost:8000/redoc>
|
|||||||
|
|
||||||
Adminer: <http://localhost:8080>
|
Adminer: <http://localhost:8080>
|
||||||
|
|
||||||
Traefik UI: <http://localhost:8090>
|
|
||||||
|
|
||||||
MailCatcher: <http://localhost:1080>
|
MailCatcher: <http://localhost:1080>
|
||||||
|
|
||||||
### Development URLs with `localhost.tiangolo.com` Configured
|
|
||||||
|
|
||||||
Development URLs, for local development.
|
|
||||||
|
|
||||||
Frontend: <http://dashboard.localhost.tiangolo.com>
|
|
||||||
|
|
||||||
Backend: <http://api.localhost.tiangolo.com>
|
|
||||||
|
|
||||||
Automatic Interactive Docs (Swagger UI): <http://api.localhost.tiangolo.com/docs>
|
|
||||||
|
|
||||||
Automatic Alternative Docs (ReDoc): <http://api.localhost.tiangolo.com/redoc>
|
|
||||||
|
|
||||||
Adminer: <http://localhost.tiangolo.com:8080>
|
|
||||||
|
|
||||||
Traefik UI: <http://localhost.tiangolo.com:8090>
|
|
||||||
|
|
||||||
MailCatcher: <http://localhost.tiangolo.com:1080>
|
|
||||||
|
|||||||
Reference in New Issue
Block a user