Skip to content

Commit 68cde75

Browse files
Merge pull request #121 from Promptly-Technologies-LLC/27-populate-deployment-page-on-documentation-website
Deployment instructions for Digital Ocean DB + Modal.com website
2 parents 712d7b6 + d30b711 commit 68cde75

File tree

5 files changed

+191
-17
lines changed

5 files changed

+191
-17
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
__pycache__
33
*.pyc
44
.env
5+
.env.local
6+
.env.development
7+
.env.production
58
_environment
69
/.quarto/
710
_docs/

docs/deployment.qmd

Lines changed: 187 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,190 @@
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+
#### Getting Started
17+
18+
- Create a [DigitalOcean](mdc:https:/www.digitalocean.com) account
19+
- Install the [`doctl` CLI tool](mdc:https:/docs.digitalocean.com/reference/doctl) and authenticate with `doctl auth init`
20+
- Install the [`psql` client](mdc:https:/www.postgresql.org/download)
21+
22+
#### Create a Project
23+
24+
Create a new project to organize your resources:
25+
26+
```bash
27+
# List existing projects
28+
doctl projects list
29+
30+
# Create a new project
31+
doctl projects create --name "YOUR-PROJECT-NAME" --purpose "YOUR-PROJECT-PURPOSE" --environment "Production"
32+
```
33+
34+
#### Set Up a Managed PostgreSQL Database
35+
36+
Create a managed, serverless PostgreSQL database instance:
37+
38+
```bash
39+
doctl databases create your-db-name --engine pg --version 17 --size db-s-1vcpu-1gb --num-nodes 1 --wait
40+
```
41+
42+
Get the database ID from the output of the create command and use it to retrieve the database connection details:
43+
44+
```bash
45+
# Get the database connection details
46+
doctl databases connection "your-database-id" --format Host,Port,User,Password,Database
47+
```
48+
49+
Store these details securely in a `.env.production` file (you will need to set them later in application deployment as production secrets):
50+
51+
```bash
52+
# Database connection parameters
53+
DB_HOST=your-host
54+
DB_PORT=your-port
55+
DB_USER=your-user
56+
DB_PASS=your-password
57+
DB_NAME=your-database
58+
```
59+
60+
You may also want to save your database id, although you can always find it again later by listing your databases with `doctl databases list`.
61+
62+
#### Setting Up a Firewall Rule (after Deploying Your Application Layer)
63+
64+
Note that by default your database is publicly accessible from the Internet, so you should create a firewall rule to restrict access to only your application's IP address once you have deployed the application. The command to do this is:
65+
66+
```bash
67+
doctl databases firewalls append <database-cluster-id> --rule <type>:<value>
68+
```
69+
70+
where `<type>` is `ip_addr` and `<value>` is the IP address of the application server. See the [DigitalOcean documentation](https://docs.digitalocean.com/reference/doctl/reference/databases/firewalls/append/) for more details.
71+
72+
**Note:** You can only complete this step after you have deployed your application layer and obtained a static IP address for the application server.
73+
74+
## Deploying and Configuring the FastAPI App
75+
76+
### On Modal.com
77+
78+
The big advantages of deploying on Modal.com are:
79+
1. that they offer $30/month of free credits for each user, plus generous additional free credit allotments for startups and researchers, and
80+
2. that it's a very user-friendly platform.
81+
82+
The disadvantages are:
83+
1. that Modal is a Python-only platform and cannot run the database layer, so you'll have to deploy that somewhere else,
84+
2. that you'll need to make some modest changes to the codebase to get it to work on Modal, and
85+
3. that Modal offers a [static IP address for the application server](https://modal.com/docs/guide/proxy-ips) only if you pay for a higher-tier plan starting at $250/year, which makes securing the database layer with a firewall rule cost prohibitive.
86+
87+
#### Getting Started
88+
89+
- [Sign up for a Modal.com account](https://modal.com/signup)
90+
- Install modal in the project directory with `uv add modal`
91+
- Run `uv run modal setup` to authenticate with Modal
92+
93+
#### Defining the Modal Image and App
94+
95+
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.
96+
97+
1. **Define the Modal Image in `deploy.py`:**
98+
- Use `modal.Image` to define the container environment. Chain methods to install dependencies and add code/files.
99+
- Start with a Debian base image matching your Python version (e.g., 3.13).
100+
- Install necessary system packages (`libpq-dev` for `psycopg2`, `libwebp-dev` for Pillow WebP support).
101+
- Install Python dependencies using `run_commands` with `uv`.
102+
- Add your local Python modules (`routers`, `utils`, `exceptions`) using `add_local_python_source`.
103+
- 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.
104+
105+
```python
106+
# deploy.py
107+
import modal
108+
import os
109+
110+
# Define the base image
111+
image = (
112+
modal.Image.debian_slim(python_version="3.13")
113+
.apt_install("libpq-dev", "libwebp-dev")
114+
.pip_install_from_pyproject("pyproject.toml")
115+
.add_local_python_source("main")
116+
.add_local_python_source("routers")
117+
.add_local_python_source("utils")
118+
.add_local_python_source("exceptions")
119+
.add_local_dir("static", remote_path="/root/static")
120+
.add_local_dir("templates", remote_path="/root/templates")
121+
)
122+
123+
# Define the Modal App
124+
app = modal.App(
125+
name="your-app-name",
126+
image=image,
127+
secrets=[modal.Secret.from_name("your-app-name-secret")]
128+
)
129+
```
130+
131+
2. **Define the ASGI App Function in `deploy.py`:**
132+
- Create a function decorated with `@app.function()` and `@modal.asgi_app()`.
133+
- Inside this function, import your FastAPI application instance from `main.py`.
134+
- Return the FastAPI app instance.
135+
- Use `@modal.concurrent()` to allow the container to handle multiple requests concurrently.
136+
137+
```python
138+
# deploy.py (continued)
139+
140+
# Define the ASGI app function
141+
@app.function(
142+
allow_concurrent_inputs=100 # Adjust concurrency as needed
143+
)
144+
@modal.asgi_app()
145+
def fastapi_app():
146+
# Important: Import the app *inside* the function
147+
# This ensures it runs within the Modal container environment
148+
# and has access to the installed packages and secrets.
149+
# It also ensures the lifespan function (db setup) runs correctly
150+
# with the environment variables provided by the Modal Secret.
151+
from main import app as web_app
152+
153+
return web_app
154+
```
155+
156+
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).
157+
158+
#### Deploying the App
159+
160+
From your terminal, in the root directory of your project, run:
161+
162+
```bash
163+
modal deploy deploy.py
164+
```
165+
166+
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`).
167+
168+
#### Setting Up Modal Secrets
169+
170+
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.
171+
172+
Create a Modal Secret either through the Modal UI or CLI. Note that the name of the secret has to match the secret name you used in the `deploy.py` file, above (e.g., `your-app-name-secret`).
173+
174+
```bash
175+
# Example using CLI
176+
modal secret create your-app-name-secret \
177+
SECRET_KEY='your_actual_secret_key' \
178+
DB_USER='your_db_user' \
179+
DB_PASSWORD='your_db_password' \
180+
DB_HOST='your_external_db_host' \
181+
DB_PORT='your_db_port' \
182+
DB_NAME='your_db_name' \
183+
RESEND_API_KEY='your_resend_api_key' \
184+
BASE_URL='https://your-username--your-app-name-serve.modal.run'
185+
```
186+
187+
**Important:** Ensure `DB_HOST` points to your *cloud* database host address, not `localhost` or `host.docker.internal`.
188+
189+
#### Testing the Deployment
190+
191+
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.

main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ async def read_home(
188188
user: Optional[User] = Depends(get_optional_user)
189189
):
190190
if user:
191-
return RedirectResponse(url="/dashboard", status_code=302)
191+
return RedirectResponse(url=app.url_path_for("read_dashboard"), status_code=302)
192192
return templates.TemplateResponse(
193193
request,
194194
"index.html",

pyproject.toml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,5 @@ dev = [
3131
"notebook<8.0.0,>=7.2.2",
3232
"pytest<9.0.0,>=8.3.3",
3333
"sqlalchemy-schemadisplay<3.0,>=2.0",
34-
"perplexity-cli",
3534
"mypy>=1.15.0",
3635
]
37-
38-
[tool.uv.sources]
39-
perplexity-cli = { git = "https://github.com/chriscarrollsmith/perplexity-cli.git" }

uv.lock

Lines changed: 0 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)