Agent-Logs-Url: https://github.com/genichin/portfolio-hub/sessions/51569572-076d-4f2a-89ad-157d9a97c42a Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
4.2 KiB
Production Deployment: HTTPS with Caddy
This guide describes how to deploy PortfolioHub behind Caddy, a reverse proxy that automatically provisions and renews TLS certificates from Let's Encrypt or ZeroSSL.
Prerequisites
- A Linux/Ubuntu server with a public IP address
- A domain name (e.g.,
api.example.com) with a DNSArecord pointing to that IP - Ports 80 and 443 open on your firewall (see Firewall Setup)
1. Install Caddy (Ubuntu/Debian)
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
| sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
| sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy
Verify the installation:
caddy version
2. Configure the Caddyfile
Create or edit /etc/caddy/Caddyfile:
your-domain.com {
reverse_proxy localhost:8000
}
Replace your-domain.com with your actual domain. Caddy automatically:
- Obtains and renews TLS certificates from Let's Encrypt/ZeroSSL
- Redirects plain HTTP (port 80) to HTTPS (port 443)
- Forwards all requests to the PortfolioHub
uvicornprocess on port 8000
Reload Caddy after editing the Caddyfile:
sudo systemctl reload caddy
3. CORS Configuration
When PortfolioHub is accessed from a browser-based frontend over HTTPS, the PORTFOLIOHUB_ALLOWED_ORIGINS environment variable must list the exact HTTPS origin(s) of your frontend.
Set the variable in your environment or .env file:
PORTFOLIOHUB_ALLOWED_ORIGINS=https://app.example.com,https://www.example.com
Multiple origins are comma-separated. For the CORS policy details see section 12 of docs/portfoliohub-spec.md.
4. Start PortfolioHub for Production
Run uvicorn without --reload and bind only to localhost so that external traffic is always routed through Caddy:
uvicorn app.main:app --host 127.0.0.1 --port 8000 --workers 2
For a systemd service, create /etc/systemd/system/portfoliohub.service:
[Unit]
Description=PortfolioHub API
After=network.target
[Service]
User=www-data
WorkingDirectory=/opt/portfoliohub
EnvironmentFile=/opt/portfoliohub/.env
ExecStart=/opt/portfoliohub/.venv/bin/uvicorn app.main:app \
--host 127.0.0.1 --port 8000 --workers 2
Restart=on-failure
[Install]
WantedBy=multi-user.target
Enable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable portfoliohub
sudo systemctl start portfoliohub
5. Docker Compose Setup (Optional)
If you run PortfolioHub in Docker, you can add Caddy as a sidecar container.
docker-compose.yml:
services:
app:
build: .
expose:
- "8000"
environment:
- PORTFOLIOHUB_ALLOWED_ORIGINS=https://your-domain.com
# add other env vars or reference an env_file
restart: unless-stopped
caddy:
image: caddy:2-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- app
restart: unless-stopped
volumes:
caddy_data:
caddy_config:
Caddyfile (placed next to docker-compose.yml):
your-domain.com {
reverse_proxy app:8000
}
Note: inside a Docker Compose network, Caddy reaches the app container via its service name (app) instead of localhost.
Start everything:
docker compose up -d
6. Firewall Setup
Open ports 80 (HTTP, needed for ACME certificate challenge) and 443 (HTTPS):
# UFW (Ubuntu)
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw reload
# firewalld (RHEL/CentOS/Fedora)
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
7. Verify the Deployment
# Health check over HTTPS
curl https://your-domain.com/health
# Expected: {"status":"ok"}
Check Caddy's status and certificate renewal:
sudo systemctl status caddy
sudo journalctl -u caddy -f