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 redir rule for the bare path (without trailing slash) is required. Without it, /icarus/ipse will 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/.

Back to Docs