Deployment
Deployment Guide — Icarus Prospects Save Editor
Deploy the Flask web app to an Ubuntu 24.04 server behind a Caddy gateway.
Architecture Overview
Internet ──▶ Caddy (gateway, TLS) ──▶ Gunicorn 0.0.0.0:5000 ──▶ Flask App
│
▼
SQLite database
(webapp/instance/icarus.db)
- Caddy on the gateway handles TLS termination and reverse proxy
- Gunicorn on the app server runs the Flask app on port 5000
- No Nginx or certbot needed on the app server
Quick Install (Automated)
SSH into the Ubuntu server and run:
# Clone the repo (or copy install.sh to the server)
git clone https://git.eurekaendeavors.com/root/icarus-prospects.git /tmp/icarus-install
sudo bash /tmp/icarus-install/deploy/install.sh
The installer handles everything:
1. Installs system packages (python3, python3-venv, git)
2. Clones the repo to /opt/icarus-prospects
3. Creates a Python venv and installs dependencies
4. Generates .env with a random SECRET_KEY
5. Sets file permissions (www-data)
6. Installs and starts the systemd service
After install, Gunicorn will be listening on 0.0.0.0:5000.
Caddy Gateway Configuration
On the Caddy gateway, add a reverse proxy entry pointing to the app server.
Root deployment (app owns the whole domain)
your-domain.com {
reverse_proxy app-server-ip:5000
}
Sub-path deployment (e.g. /icarus/ipse)
When the app is mounted at a sub-path behind other services, use handle
(not handle_path) so the full URI reaches the backend, where
PrefixMiddleware in wsgi.py strips the prefix and sets SCRIPT_NAME:
your-domain.com {
# Redirect bare path to trailing-slash so browsers don't get a blank page
@ipse_bare path /icarus/ipse
redir @ipse_bare /icarus/ipse/ 308
handle /icarus/ipse/* {
reverse_proxy app-server-ip:5000
}
# ... other handle blocks for other services ...
}
Important: The
redirrule for the bare path (without trailing slash) is required. Without it,/icarus/ipsewill never reach the backend and users will see a blank page.
Caddy will automatically provision a TLS certificate via Let's Encrypt.
Manual Install (Step by Step)
If you prefer to do it manually instead of using the installer:
1. System Packages
sudo apt update && sudo apt install -y python3 python3-venv python3-pip git
2. Clone the Repository
sudo mkdir -p /opt/icarus-prospects
sudo chown $USER:$USER /opt/icarus-prospects
git clone https://git.eurekaendeavors.com/root/icarus-prospects.git /opt/icarus-prospects
cd /opt/icarus-prospects
3. Python Environment
python3 -m venv venv
source venv/bin/activate
pip install -r webapp/requirements.txt
4. Environment Variables
# Generate a secret key
python3 -c "import secrets; print(secrets.token_hex(32))"
# Create .env
cat > .env <<EOF
FLASK_ENV=prod
SECRET_KEY=<paste-your-key-here>
DATABASE_URL=sqlite:////opt/icarus-prospects/webapp/instance/icarus.db
MAX_UPLOAD_MB=50
SESSION_TTL=3600
EOF
chmod 600 .env
5. Smoke Test
source venv/bin/activate
export $(cat .env | xargs)
gunicorn wsgi:app --bind 0.0.0.0:5000 --workers 1
# Visit http://server-ip:5000 — should see the upload page
# Ctrl+C to stop
6. Systemd Service
sudo cp deploy/icarus-prospects.service /etc/systemd/system/
sudo mkdir -p /var/log/icarus-prospects
sudo chown www-data:www-data /var/log/icarus-prospects
sudo chown -R www-data:www-data /opt/icarus-prospects
sudo systemctl daemon-reload
sudo systemctl enable icarus-prospects
sudo systemctl start icarus-prospects
Key Files
| File | Purpose |
|---|---|
wsgi.py |
WSGI entry point — creates the Flask app in prod mode |
webapp/config.py |
Configuration classes (DevConfig, ProdConfig, TestConfig) |
deploy/install.sh |
Automated installer script for Ubuntu 24.04 |
deploy/.env.example |
Template for environment variables |
deploy/icarus-prospects.service |
Systemd unit file (Gunicorn on 0.0.0.0:5000) |
Environment Variables
| Variable | Default | Description |
|---|---|---|
FLASK_ENV |
dev |
Set to prod for production |
SECRET_KEY |
random | Must be set to a fixed value in production |
DATABASE_URL |
sqlite:///webapp/instance/icarus.db |
Database connection string |
MAX_UPLOAD_MB |
50 |
Maximum upload file size in MB |
SESSION_TTL |
3600 |
Edit session lifetime in seconds |
SCRIPT_NAME |
(empty) | Sub-path prefix for reverse proxy (e.g. /icarus/ipse) |
Operations
Check status
sudo systemctl status icarus-prospects
View logs
journalctl -u icarus-prospects -f # systemd logs
tail -f /var/log/icarus-prospects/access.log # Gunicorn access
tail -f /var/log/icarus-prospects/error.log # Gunicorn errors
Update the application
cd /opt/icarus-prospects
sudo -u www-data git pull origin master
sudo systemctl restart icarus-prospects
Re-run the installer (safe for updates)
sudo bash /opt/icarus-prospects/deploy/install.sh
The installer detects an existing installation and pulls updates instead of re-cloning. It preserves the existing .env file.
Troubleshooting
ModuleNotFoundError:
cd /opt/icarus-prospects && source venv/bin/activate
python -c "from webapp.app import create_app; print('OK')"
Permission denied on SQLite:
sudo chown -R www-data:www-data /opt/icarus-prospects/webapp/instance/
Can't reach port 5000 from Caddy gateway:
# Check if Gunicorn is running and listening
ss -tlnp | grep 5000
# Check firewall
sudo ufw status
sudo ufw allow 5000/tcp # if needed
Blank page / 404 at /icarus/ipse but works at /icarus/ipse/:
This means Caddy's handle or handle_path glob (/icarus/ipse/*) does
not match the bare path without a trailing slash. The request never reaches
Gunicorn — Caddy returns an empty 200 itself. Fix by adding a redirect rule
to the Caddyfile on the gateway:
@ipse_bare path /icarus/ipse
redir @ipse_bare /icarus/ipse/ 308
Verify with: curl -v https://your-domain.com/icarus/ipse — you should
see a 308 Permanent Redirect to /icarus/ipse/.