# Production Deployment: HTTPS with Caddy This guide describes how to deploy PortfolioHub behind [Caddy](https://caddyserver.com/), 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 DNS `A` record pointing to that IP - Ports **80** and **443** open on your firewall (see [Firewall Setup](#firewall-setup)) --- ## 1. Install Caddy (Ubuntu/Debian) ```bash 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: ```bash 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 `uvicorn` process on port 8000 Reload Caddy after editing the Caddyfile: ```bash 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: ```bash 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: ```bash uvicorn app.main:app --host 127.0.0.1 --port 8000 --workers 2 ``` For a systemd service, create `/etc/systemd/system/portfoliohub.service`: ```ini [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: ```bash 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`: ```yaml 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: ```bash docker compose up -d ``` --- ## 6. Firewall Setup Open ports 80 (HTTP, needed for ACME certificate challenge) and 443 (HTTPS): ```bash # UFW (Ubuntu) sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw reload ``` ```bash # 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 ```bash # Health check over HTTPS curl https://your-domain.com/health # Expected: {"status":"ok"} ``` Check Caddy's status and certificate renewal: ```bash sudo systemctl status caddy sudo journalctl -u caddy -f ```