Development Backend Deployment Setup¶
Overview¶
This document details the complete setup and configuration for deploying the FastAPI backend to the Virtuozzo Application Platform development server (dev-backend.mightybox.site).
Infrastructure Details¶
Platform: Virtuozzo Application Platform (Jelastic PaaS)
Server: Apache 2.4.66 with mod_wsgi 4.9.4
Python: 3.14.2
Environment: /var/www/webroot/
Virtual Environment: /var/www/webroot/virtenv/
Application Root: /var/www/webroot/ROOT/
Deployment Architecture¶
GitHub Actions Workflow¶
File: .github/workflows/deploy-backend.yml
Trigger: Push to development branch affecting backend/** or manual workflow_dispatch
Deployment Steps¶
- Package Creation
- Checkout code
- Create tar.gz package with:
app/,requirements.txt,pyproject.toml,alembic.ini,wsgi.py -
Excludes:
__pycache__,*.pyc,*.egg-info -
File Transfer
- Uses SFTP (bypasses shell to avoid output interference)
-
Uploads package to
/tmp/backend-deploy.tar.gz -
Deployment Execution
- Backup existing deployment
- Extract new files to
/var/www/webroot/ROOT/ - Create/update virtual environment
- Install dependencies in venv
- Configure Apache environment variables
- Run database migrations
- Restart Apache
Critical Issues Resolved¶
1. SSH Key Formatting¶
Problem: echo command strips newlines from SSH private keys, causing "error in libcrypto".
Solution:
Why: printf preserves exact formatting including required newlines for OpenSSH keys.
2. Remote Shell Output Interference¶
Problem: Apache user's shell produced output during SSH sessions, causing:
- scp: "Received message too long 1416128883" (hex: 0x54686973 = "This")
- rsync: "protocol version mismatch -- is your shell clean?"
Initial Issue: Apache user had /sbin/nologin shell.
Solution:
Better Approach: Used SFTP which bypasses shell entirely.
3. Virtual Environment Requirement¶
Problem: Virtuozzo platform expects applications to use virtual environments at /var/www/webroot/virtenv/.
Apache Config Reference:
Solution:
# Create virtual environment
python3 -m venv /var/www/webroot/virtenv
# Install packages in venv
/var/www/webroot/virtenv/bin/pip install -r requirements.txt
# Run migrations in venv
/var/www/webroot/virtenv/bin/alembic upgrade head
4. WSGI_SCRIPT Environment Variable¶
Problem: Apache config uses ${WSGI_SCRIPT} variable but it wasn't defined, causing "Config variable not defined" warnings.
Apache Config:
Solution:
Note: Systemd EnvironmentFile requires KEY=VALUE format, not export KEY=VALUE.
5. PyO3 Subinterpreter Incompatibility (Critical)¶
Problem: FastAPI uses Pydantic, which depends on pydantic-core (Rust extension via PyO3). PyO3 doesn't support Python subinterpreters used by mod_wsgi by default.
Error:
ImportError: PyO3 modules do not yet support subinterpreters
https://github.com/PyO3/pyo3/issues/576
Solution: Force mod_wsgi to use the main Python interpreter:
Implementation in workflow:
if ! sudo grep -q "WSGIApplicationGroup %{GLOBAL}" /etc/httpd/conf.d/wsgi.conf; then
sudo sed -i 's/WSGIProcessGroup apache/WSGIProcessGroup apache\nWSGIApplicationGroup %{GLOBAL}/' /etc/httpd/conf.d/wsgi.conf
fi
WSGI Configuration¶
wsgi.py Entry Point¶
File: backend/wsgi.py
import sys
import os
# Add the application directory to the Python path
sys.path.insert(0, os.path.dirname(__file__))
# Import the FastAPI ASGI application
from app.main import app
# FastAPI is ASGI, but mod_wsgi expects WSGI
# Use a2wsgi to convert ASGI app to WSGI
from a2wsgi import ASGIMiddleware
application = ASGIMiddleware(app)
Key Points:
- FastAPI is an ASGI application
- mod_wsgi expects WSGI applications
- a2wsgi package provides ASGI-to-WSGI adapter
- The WSGI server expects a callable named application
Dependencies¶
Added to pyproject.toml:
Apache Configuration¶
mod_wsgi Settings¶
File: /etc/httpd/conf.d/wsgi.conf
WSGIDaemonProcess apache user=apache group=apache processes=2 threads=10 \
python-path="/var/www/webroot/virtenv/lib/python/:/var/www/webroot/" \
home="/var/www/webroot/"
WSGIScriptAlias / ${WSGI_SCRIPT}
WSGISocketPrefix "/tmp/wsgi"
WSGIPassAuthorization On
WSGIProcessGroup apache
WSGIApplicationGroup %{GLOBAL} # Critical for PyO3/Pydantic
Environment Variables¶
File: /usr/local/etc/httpd_vars
Loaded by: /etc/systemd/system/httpd.service
GitHub Secrets Configuration¶
Required secrets in repository settings:
| Secret Name | Description | Example Value |
|---|---|---|
DEV_SSH_PRIVATE_KEY |
SSH private key for apache user | Full OpenSSH private key with headers |
DEV_SSH_HOST |
Server hostname/IP | 15.204.15.210 |
DEV_SERVER_USER |
SSH username | apache |
SSH Key Format: The secret should contain a complete OpenSSH private key including the BEGIN and END headers.
Important: Must include newline after the END header.
Testing the Deployment¶
Manual Deployment Trigger¶
Verify Deployment¶
# Test API root
curl https://dev-backend.mightybox.site/
# Expected: {"detail":"Not Found"}
# Test Swagger UI
curl https://dev-backend.mightybox.site/docs
# Expected: HTML page with Swagger UI
# Test OpenAPI schema
curl https://dev-backend.mightybox.site/openapi.json
# Expected: JSON with full API schema
Check Logs¶
# Apache error log
ssh apache@server 'sudo tail -f /var/log/httpd/error_log'
# SSL-specific errors
ssh apache@server 'sudo tail -f /var/log/httpd/ssl-error.log'
Troubleshooting¶
500 Internal Server Error with No Logs¶
Symptom: Apache returns 500 errors but no Python tracebacks in logs.
Check:
1. Verify WSGI_SCRIPT environment variable is set
2. Check if PyO3 subinterpreter error exists in SSL logs
3. Ensure WSGIApplicationGroup %{GLOBAL} is in wsgi.conf
ImportError: Cannot import FastAPI modules¶
Symptom: Python import errors for FastAPI, Pydantic, or other packages.
Check:
1. Virtual environment exists at /var/www/webroot/virtenv/
2. Packages installed in venv: /var/www/webroot/virtenv/bin/pip list
3. Python path in Apache config points to venv
SSH Connection Failures¶
Symptom: "Permission denied" or "error in libcrypto"
Check:
1. SSH key format in GitHub secret (proper headers/footers with newlines)
2. Key permissions on server (should be 600)
3. Apache user shell (should be /bin/bash)
SFTP "Received message too long"¶
Symptom: File transfer fails with message length error.
Check:
1. Remote shell producing output during non-interactive sessions
2. Check apache user's .bashrc for echo statements
3. Wrap output in interactive check: if [ -t 0 ]; then echo "message"; fi
Deployment Workflow Summary¶
graph TD
A[Push to development] --> B[GitHub Actions Triggered]
B --> C[Create deployment package]
C --> D[Setup SSH with printf]
D --> E[SFTP upload to /tmp/]
E --> F[SSH to server]
F --> G[Backup existing deployment]
G --> H[Extract new files]
H --> I[Create/update venv]
I --> J[Install dependencies in venv]
J --> K[Set WSGI_SCRIPT variable]
K --> L[Configure PyO3 compatibility]
L --> M[Run migrations]
M --> N[Restart Apache]
N --> O[Cleanup]
References¶
Maintenance¶
Updating Dependencies¶
Dependencies are installed from requirements.txt which uses editable install (-e .), pulling from pyproject.toml.
To update dependencies:
1. Modify backend/pyproject.toml
2. Commit and push to development branch
3. Deployment workflow automatically installs updated packages
Database Migrations¶
Migrations run automatically during deployment if alembic directory exists:
Manual Rollback¶
Backups are created before each deployment:
To rollback:
cd /var/www/webroot
sudo rm -rf ROOT
sudo cp -r backups/ROOT.backup.YYYYMMDD_HHMMSS ROOT
sudo systemctl restart httpd
Security Considerations¶
- SSH Keys: Private keys stored as GitHub encrypted secrets
- File Permissions: Deployment sets proper ownership (
apache:apache) - Virtual Environment: Isolates application dependencies
- HTTPS: SSL/TLS enforced via Apache configuration
- Authentication:
WSGIPassAuthorization Onpasses auth headers to application
Performance¶
Apache Configuration: - Processes: 2 - Threads per process: 10 - Max concurrent requests: 20
Optimization Notes: - Virtual environment cached between deployments - Only changed files deployed (tar.gz package) - Database migrations run incrementally - Apache graceful restart minimizes downtime
Last Updated: 2026-01-19 Deployment URL: https://dev-backend.mightybox.site Status: ✅ Production-ready