Skip to content

Commit 1d2135f

Browse files
First draft of app deployment instructions
1 parent 289d9f5 commit 1d2135f

File tree

1 file changed

+133
-1
lines changed

1 file changed

+133
-1
lines changed

docs/deployment.qmd

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,136 @@
22
title: "Deployment"
33
---
44

5-
## Under construction
5+
This application requires two services to be deployed and connected to each other:
6+
7+
1. A PostgreSQL database (the storage layer)
8+
2. A FastAPI app (the application layer)
9+
10+
There are *many* hosting options available for each of these services; this guide will cover only a few of them.
11+
12+
## Deploying and Configuring the PostgreSQL Database
13+
14+
### On Digital Ocean
15+
16+
## Deploying and Configuring the FastAPI App
17+
18+
### On Modal.com
19+
20+
The big advantages of deploying on Modal.com are:
21+
1. that they offer $30/month of free credits for each user, plus generous additional free credit allotments for startups and researchers, and
22+
2. that it's a very user-friendly platform.
23+
24+
The disadvantages are:
25+
1. that Modal is a Python-only platform and cannot run the database layer, so you'll have to deploy that somewhere else, and
26+
2. that you'll need to make some modest changes to the codebase to get it to work on Modal.
27+
28+
#### Getting Started
29+
30+
- [Sign up for a Modal.com account](https://modal.com/signup)
31+
- Install modal in the project directory with `uv add modal`
32+
- Run `uv run modal setup` to authenticate with Modal
33+
34+
#### Setting Up Modal Secrets
35+
36+
The application relies on environment variables stored in `.env` (like `SECRET_KEY`, `DB_USER`, `DB_PASSWORD`, `DB_HOST`, `DB_PORT`, `DB_NAME`, `RESEND_API_KEY`, `BASE_URL`). These sensitive values should be stored securely using Modal Secrets.
37+
38+
Create a Modal Secret (e.g., named `fastapi-webapp-secrets`) either through the Modal UI or CLI:
39+
```bash
40+
# Example using CLI
41+
modal secret create your-app-name-secret \
42+
SECRET_KEY='your_actual_secret_key' \
43+
DB_USER='your_db_user' \
44+
DB_PASSWORD='your_db_password' \
45+
DB_HOST='your_external_db_host' \
46+
DB_PORT='your_db_port' \
47+
DB_NAME='your_db_name' \
48+
RESEND_API_KEY='your_resend_api_key' \
49+
BASE_URL='https://your-modal-app-url' # Usually https://your-org-name--your-app-name-serve.modal.run
50+
```
51+
52+
**Important:** Ensure `DB_HOST` points to your *cloud* database host address, not `localhost` or `host.docker.internal`.
53+
54+
#### Defining the Modal Image and App
55+
56+
Create a new Python file in the root of your project, for example, `deploy.py`. This file will define the Modal Image and the ASGI app deployment.
57+
58+
1. **Define the Modal Image in `deploy.py`:**
59+
- Use `modal.Image` to define the container environment. Chain methods to install dependencies and add code/files.
60+
- Start with a Debian base image matching your Python version (e.g., 3.13).
61+
- Install necessary system packages (`libpq-dev` for `psycopg2`, `libwebp-dev` for Pillow WebP support).
62+
- Install Python dependencies using `run_commands` with `uv`.
63+
- Add your local Python modules (`routers`, `utils`, `exceptions`) using `add_local_python_source`.
64+
- Add the `static` and `templates` directories using `add_local_dir`. The default behaviour (copying on container startup) is usually fine for development, but consider `copy=True` for production stability if these files are large or rarely change.
65+
66+
```python
67+
# deploy.py
68+
import modal
69+
import os
70+
71+
# Define the base image
72+
image = (
73+
modal.Image.debian_slim(python_version="3.13")
74+
.apt_install("libpq-dev", "libwebp-dev")
75+
.pip_install("uv")
76+
.run_commands(
77+
# Install dependencies from pyproject.toml using uv
78+
# Make sure uv sync handles psycopg2 correctly with libpq-dev present
79+
"uv sync --no-dev --system --compile-bytecode",
80+
)
81+
.add_local_python_source("routers")
82+
.add_local_python_source("utils")
83+
.add_local_python_source("exceptions")
84+
.add_local_dir("static", remote_path="/static")
85+
.add_local_dir("templates", remote_path="/templates")
86+
)
87+
88+
# Define the Modal App
89+
app = modal.App(
90+
name="fastapi-webapp", # Optional: name for your Modal app
91+
image=image,
92+
secrets=[modal.Secret.from_name("fastapi-webapp-secrets")]
93+
)
94+
```
95+
96+
2. **Define the ASGI App Function in `deploy.py`:**
97+
- Create a function decorated with `@app.function()` and `@modal.asgi_app()`.
98+
- Inside this function, import your FastAPI application instance from `main.py`.
99+
- Return the FastAPI app instance.
100+
- Use `@modal.concurrent()` to allow the container to handle multiple requests concurrently.
101+
102+
```python
103+
# deploy.py (continued)
104+
105+
# Define the ASGI app function
106+
@app.function(
107+
allow_concurrent_inputs=100 # Adjust concurrency as needed
108+
)
109+
@modal.asgi_app()
110+
def fastapi_app():
111+
# Important: Import the app *inside* the function
112+
# This ensures it runs within the Modal container environment
113+
# and has access to the installed packages and secrets.
114+
# It also ensures the lifespan function (db setup) runs correctly
115+
# with the environment variables provided by the Modal Secret.
116+
from main import app as web_app
117+
118+
return web_app
119+
```
120+
121+
For more information on Modal FastAPI images and applications, see [this guide](https://modal.com/docs/guide/webhooks#how-do-web-endpoints-run-in-the-cloud).
122+
123+
#### Deploying the App
124+
125+
1. From your terminal, in the root directory of your project, run:
126+
```bash
127+
modal deploy deploy.py
128+
```
129+
Modal will build the image (if it hasn't been built before or if dependencies changed) and deploy the ASGI app. It will output a public URL (e.g., `https://your-username--your-app-name.modal.run`).
130+
131+
2. Once you have the public URL from Modal, make sure the `BASE_URL` variable in your Modal Secret matches this public URL. This is crucial for generating correct absolute URLs (e.g., in password reset emails). If you need to update the secret, run:
132+
```bash
133+
modal secret update your-app-name-secret BASE_URL='https://your-modal-app-url'
134+
```
135+
Redeploy if necessary (`modal deploy deploy.py`), although changes to secrets often take effect on subsequent container starts without a full redeploy.
136+
137+
3. Access the provided Modal URL in your browser. Browse the site and test the registration and password reset features to ensure database and Resend connections work correctly.

0 commit comments

Comments
 (0)