Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

📝 Update deployment files and docs #660

Merged
merged 10 commits into from
Mar 9, 2024
5 changes: 0 additions & 5 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,6 @@ SENTRY_DSN=
# Flower
FLOWER_BASIC_AUTH=

# Traefik
TRAEFIK_PUBLIC_NETWORK=traefik-public
TRAEFIK_TAG=traefik
TRAEFIK_PUBLIC_TAG=traefik-public

# Configure these with your own Docker registry images
DOCKER_IMAGE_BACKEND=backend
DOCKER_IMAGE_CELERYWORKER=celery
Expand Down
3 changes: 0 additions & 3 deletions backend/app/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ class Settings(BaseSettings):
# 60 minutes * 24 hours * 8 days = 8 days
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8
SERVER_HOST: AnyHttpUrl
# BACKEND_CORS_ORIGINS is a JSON-formatted list of origins
# e.g: '["http://localhost", "http://localhost:4200", "http://localhost:3000", \
# "http://localhost:8080", "http://local.dockertoolbox.tiangolo.com"]'
BACKEND_CORS_ORIGINS: list[AnyHttpUrl] | str = []

@field_validator("BACKEND_CORS_ORIGINS", mode="before")
Expand Down
121 changes: 110 additions & 11 deletions deployment.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,124 @@
# FastAPI Project - Deployment

You can deploy the using Docker Compose with a main Traefik proxy outside handling communication to the outside world and HTTPS certificates.
You can deploy the project using Docker Compose in a remote server.

And you can use CI (continuous integration) systems to do it automatically.
It expects you to have a Traefik proxy handling communication to the outside world and HTTPS certificates.

And you can use CI (continuous integration) systems to deploy automatically.

But you have to configure a couple things first.

## Traefik network
## Preparation

* Have a remote server ready and available.
* Configure the DNS records of your domain to point to the IP of the server you just created.
* Install and configure [Docker](https://docs.docker.com/engine/install/).
* Create a remote directory to store your code, for example:

```bash
mkdir -p /root/code/fastapi-project/
```

## Public Traefik

We need a Traefik proxy to handle incoming connections and HTTPS certificates.

### Traefik Docker Compose

Copy the Traefik Docker Compose file to your server, to your code directory. You could do it with `rsync`:

```bash
rsync -a docker-compose.traefik.yml root@your-server.example.com:/root/code/fastapi-project/
```

### 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.

This stack expects the public Traefik network to be named `traefik-public`.
To create a Docker "public network" named `traefik-public` run:

If you need to use a different Traefik public network name, update it in the `docker-compose.yml` files, in the section:
```bash
docker network create traefik-public
```

### Traefik Environment Variables

The Traefik Docker Compose file expects some environment variables to be set.

```YAML
networks:
traefik-public:
external: true
Create the environment variables for HTTP Basic Auth.

* Create the username, e.g.:

```bash
export USERNAME=admin
```

* Create an environment variable with the password, e.g.:

```bash
export PASSWORD=changethis
```

* Use openssl to generate the "hashed" version of the password and store it in an environment variable:

```bash
export HASHED_PASSWORD=$(openssl passwd -apr1 $PASSWORD)
```

Change `traefik-public` to the name of the used Traefik network. And then update it in the file `.env`:
* Create an environment variable with the domain name, e.g.:

```bash
TRAEFIK_PUBLIC_NETWORK=traefik-public
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
```

### Start the Traefik Docker Compose

Now with the environment variables set and the `docker-compose.traefik.yml` in place, you can start the Traefik Docker Compose:

```bash
docker compose -f docker-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.

You could configure the variables in the `.env` file to match your domain, or you could override them before running the `docker compose` command.

For example:

```bash
export DOMAIN=fastapi-project.example.com
```

And then deploy with Docker Compose:

```bash
docker compose -f docker-compose.yml up -d
```

For production you wouldn't want to have the overrides in `docker-compose.override.yml`, so you would need to explicitly specify the file to use, `docker-compose.yml`.

## URLs

Replace `fastapi-project.example.com` with your domain:

Frontend: https://fastapi-project.example.com

Backend API docs: https://fastapi-project.example.com/docs

Backend API base URL: https://fastapi-project.example.com/api/

PGAdmin: https://pgadmin.fastapi-project.example.com

Flower: https://flower.fastapi-project.example.com

Traefik UI: https://traefik.fastapi-project.example.com
25 changes: 21 additions & 4 deletions docker-compose.override.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ version: "3.3"
services:

proxy:
image: traefik:v2.3
volumes:
- /var/run/docker.sock:/var/run/docker.sock
ports:
- "80:80"
- "8090:8080"
Expand All @@ -10,10 +13,13 @@ services:
# 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
# from the env var TRAEFIK_TAG
- --providers.docker.constraints=Label(`traefik.constraint-label-stack`, `${TRAEFIK_TAG?Variable not set}`)
- --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
Expand All @@ -23,8 +29,12 @@ services:
# Enable the Dashboard and API in insecure mode for local development
- --api.insecure=true
labels:
- traefik.http.routers.${STACK_NAME?Variable not set}-traefik-public-http.rule=Host(`${DOMAIN?Variable not set}`)
- traefik.http.services.${STACK_NAME?Variable not set}-traefik-public.loadbalancer.server.port=80
# 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

db:
ports:
Expand Down Expand Up @@ -72,6 +82,13 @@ services:
args:
INSTALL_DEV: ${INSTALL_DEV-true}

frontend:
build:
context: ./frontend
args:
- VITE_API_URL=http://${DOMAIN?Variable not set}
- NODE_ENV=development

networks:
traefik-public:
# For local dev, don't expect an external Traefik network
Expand Down
77 changes: 77 additions & 0 deletions docker-compose.traefik.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
services:
traefik:
image: traefik:v2.3
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
Loading
Loading