Production Deployment¶
This guide covers deploying Pipelit on a bare-metal server or VM with production-grade process management, frontend builds, and security hardening.
Overview¶
A production Pipelit deployment consists of:
- Gunicorn with Uvicorn workers serving the FastAPI application
- RQ worker processing background jobs
- Redis 8.0+ for task queuing and pub/sub
- A reverse proxy (Nginx or Caddy) handling HTTPS and WebSocket upgrade
- A database (SQLite for small deployments, PostgreSQL recommended)
Building the Frontend¶
In production, the frontend is compiled into static files and served directly by FastAPI. There is no need for the Vite dev server.
This produces optimized static assets in platform/frontend/dist/. FastAPI automatically serves these files and the SPA's index.html for all non-API routes.
Tip
Run npm run build once after each update. The built files are served directly by FastAPI at http://your-server:8000/.
Gunicorn with Uvicorn Workers¶
For production, use Gunicorn as the process manager with Uvicorn workers:
cd platform
source ../.venv/bin/activate
gunicorn main:app \
--worker-class uvicorn.workers.UvicornWorker \
--workers 4 \
--bind 0.0.0.0:8000 \
--timeout 120 \
--graceful-timeout 30 \
--access-logfile - \
--error-logfile -
Worker count: A common rule of thumb is (2 * CPU_CORES) + 1. For a 2-core server, use 4--5 workers.
WebSocket and worker count
Each WebSocket connection is held open by a single worker. With Gunicorn's pre-fork model, WebSocket connections are pinned to the worker that accepted them. Ensure you have enough workers to handle both HTTP requests and persistent WebSocket connections.
Process Management with systemd¶
Create systemd service files to ensure Pipelit starts on boot and restarts on failure.
Backend Service¶
[Unit]
Description=Pipelit Backend
After=network.target redis.service
Requires=redis.service
[Service]
Type=simple
User=pipelit
Group=pipelit
WorkingDirectory=/opt/pipelit/platform
Environment="PATH=/opt/pipelit/.venv/bin:/usr/bin"
EnvironmentFile=/opt/pipelit/.env
ExecStart=/opt/pipelit/.venv/bin/gunicorn main:app \
--worker-class uvicorn.workers.UvicornWorker \
--workers 4 \
--bind 127.0.0.1:8000 \
--timeout 120 \
--access-logfile -
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
RQ Worker Service¶
[Unit]
Description=Pipelit RQ Worker
After=network.target redis.service
Requires=redis.service
[Service]
Type=simple
User=pipelit
Group=pipelit
WorkingDirectory=/opt/pipelit/platform
Environment="PATH=/opt/pipelit/.venv/bin:/usr/bin"
EnvironmentFile=/opt/pipelit/.env
ExecStart=/opt/pipelit/.venv/bin/rq worker workflows --with-scheduler
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Enable and Start¶
sudo systemctl daemon-reload
sudo systemctl enable pipelit pipelit-worker
sudo systemctl start pipelit pipelit-worker
# Check status
sudo systemctl status pipelit
sudo systemctl status pipelit-worker
# View logs
sudo journalctl -u pipelit -f
sudo journalctl -u pipelit-worker -f
Environment File¶
Create /opt/pipelit/.env with production values:
FIELD_ENCRYPTION_KEY=your-generated-fernet-key
SECRET_KEY=a-long-random-string-here
DEBUG=false
ALLOWED_HOSTS=your-domain.com
CORS_ALLOW_ALL_ORIGINS=false
REDIS_URL=redis://localhost:6379/0
DATABASE_URL=sqlite:////opt/pipelit/platform/db.sqlite3
See Environment Variables for the full reference.
Security Checklist¶
Before exposing Pipelit to the internet, verify each item:
- [ ]
SECRET_KEYis set to a long, random value (notchange-me-in-production) - [ ]
DEBUGis set tofalse - [ ]
CORS_ALLOW_ALL_ORIGINSis set tofalse - [ ]
ALLOWED_HOSTSis set to your actual domain name(s) - [ ]
FIELD_ENCRYPTION_KEYis generated and stored securely (never committed to version control) - [ ] HTTPS is configured via a reverse proxy with valid SSL certificates
- [ ] Firewall blocks direct access to ports 8000 (backend) and 6379 (Redis) from the public internet
- [ ] Redis is bound to
127.0.0.1or uses authentication (Redis does not require a password by default) - [ ] Database file (if using SQLite) has restrictive file permissions (
chmod 600) - [ ] System user runs Pipelit with minimal privileges (do not run as root)
- [ ] MFA is enabled for admin accounts via the Settings page
Never expose Redis to the internet
Redis has no authentication by default. Always bind Redis to 127.0.0.1 or use a firewall to restrict access. An exposed Redis instance can be trivially exploited.
Reverse Proxy¶
A reverse proxy is strongly recommended for production to handle HTTPS termination and WebSocket upgrades. See the Reverse Proxy guide for Nginx and Caddy configurations.
Updating in Production¶
cd /opt/pipelit
git pull
# Rebuild frontend
cd platform/frontend && npm install && npm run build && cd ../..
# Restart services (migrations run automatically on startup)
sudo systemctl restart pipelit pipelit-worker
Alembic migrations are applied automatically when the FastAPI application starts. The lifespan handler in main.py calls Base.metadata.create_all() on startup for development convenience. In production, you can also run migrations explicitly:
Monitoring¶
Monitor the following for a healthy deployment:
- systemd service status -- both
pipelitandpipelit-workershould beactive (running) - Redis connectivity --
redis-cli pingshould returnPONG - Worker queue depth --
rq infoshows pending/active/failed job counts - Application logs -- watch for unhandled exceptions via
journalctl - Disk space -- SQLite databases and Redis persistence files grow over time