Skip to content

GitHub to cPanel Deployment Guide

Version: 2.0 | Updated: February 2026 | Stack: Python/FastAPI + Vanilla JS + PostgreSQL


Overview

Automated deployment from GitHub to cPanel hosting via GitHub Actions and FTP. No SSH required.

Local Development → Git Push (main) → GitHub Actions → FTP Upload → cPanel → Live

What gets deployed:

  • Backend: main.py, config.py, models.py, requirements.txt (root-level Python files)
  • Frontend: frontend/ directory (HTML, CSS, JS, images, fonts)
  • cPanel restarts the Python app automatically via Passenger

Prerequisites

  • GitHub repository with Actions enabled
  • cPanel hosting account with:
    • FTP access (credentials from cPanel → FTP Accounts)
    • "Setup Python App" feature (CloudLinux/LiteSpeed)
    • PostgreSQL database support
    • File Manager and Terminal (web-based)
  • Local development environment with Git installed

One-Time cPanel Setup

These steps are done once through the cPanel web interface. No SSH needed.

1. Create PostgreSQL Database

  1. Log into cPanel
  2. Go to PostgreSQL Databases (or MySQL Databases if PostgreSQL unavailable)
  3. Create a new database (e.g., username_appdb)
  4. Create a database user with a strong password
  5. Add user to database with All Privileges
  6. Note the connection details:
FieldValue
Hostlocalhost
Port5432
Databaseusername_appdb
Userusername_dbuser
Password(your password)

2. Setup Python App

  1. In cPanel, go to Setup Python App
  2. Click Create Application
  3. Configure:
SettingValue
Python version3.10+ (latest available)
Application rootpublic_html/app (your subdirectory)
Application URLyourdomain.com/app
Application startup filemain.py
Application Entry pointapp
  1. Click Create
  2. Note the virtual environment path displayed (e.g., /home/username/virtualenv/public_html/app/3.10/)

3. Upload Initial Configuration

  1. Go to cPanel → File Manager
  2. Navigate to your application root (public_html/app)
  3. Create a new file: .env
  4. Paste your production environment configuration:
env
ENV=production
DATABASE_URL=postgresql://username_dbuser:yourpassword@localhost:5432/username_appdb
SECRET_KEY=generate-a-64-char-random-string-here
APP_NAME=MyApp
APP_VERSION=1.0.0
HOST=0.0.0.0
PORT=8668
LOG_LEVEL=INFO

Generate SECRET_KEY at any random string generator — use 64+ characters.

4. Install Dependencies (One-Time)

  1. In cPanel, go to Terminal (web-based terminal, not SSH)
  2. Activate the Python virtual environment:
bash
source /home/username/virtualenv/public_html/app/3.10/bin/activate
  1. Install dependencies:
bash
cd ~/public_html/app
pip install -r requirements.txt
  1. Verify the app starts:
bash
python main.py

Press Ctrl+C to stop. The app will run via Passenger in production.

5. Create Passenger WSGI File

In File Manager, create passenger_wsgi.py in your application root:

python
import sys
import os

# Add application directory to path
app_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, app_dir)

# Load environment variables
from dotenv import load_dotenv
load_dotenv(os.path.join(app_dir, '.env'))

# Import FastAPI app
from main import app as application

6. Create Restart Directory

In File Manager, create a tmp directory inside your application root. Then create an empty file restart.txt inside it:

public_html/app/
├── tmp/
│   └── restart.txt    ← Passenger watches this file
├── main.py
├── config.py
├── ...

Passenger restarts the app whenever restart.txt is modified.


GitHub Secrets Setup

Store FTP credentials as encrypted secrets in your GitHub repository.

Add Secrets

  1. Go to your GitHub repository
  2. Navigate to SettingsSecrets and variablesActions
  3. Click New repository secret for each:
Secret NameValueWhere to Find
FTP_SERVERftp.yourdomain.comcPanel → FTP Accounts
FTP_USERNAMEdeploy@yourdomain.comcPanel → FTP Accounts
FTP_PASSWORDFTP account passwordSet when creating FTP account

Instead of using your main cPanel FTP account:

  1. Go to cPanel → FTP Accounts
  2. Create a new account:
    • Username: deploy (becomes deploy@yourdomain.com)
    • Password: strong generated password
    • Directory: /public_html/app (restrict to app directory)
  3. Use these credentials in GitHub Secrets

GitHub Actions Workflow

Create the workflow file at .github/workflows/deploy.yml in your repository.

yaml
name: Deploy to cPanel

on:
  push:
    branches:
      - main

jobs:
  deploy:
    name: FTP Deploy
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Deploy via FTP
        uses: SamKirkland/FTP-Deploy-Action@v4.3.5
        with:
          server: ${{ secrets.FTP_SERVER }}
          username: ${{ secrets.FTP_USERNAME }}
          password: ${{ secrets.FTP_PASSWORD }}
          server-dir: /public_html/app/
          protocol: ftps
          port: 21
          exclude: |
            **/.git*
            **/.git*/**
            .github/**
            .claude/**
            .claudeconfig/**
            .claude-flow/**
            .hive-mind/**
            .swarm/**
            .vscode/**
            _TEMP/**
            docs/**
            scripts/**
            templates/**
            venv/**
            __pycache__/**
            node_modules/**
            coordination/**
            memory/**
            docker-compose.yml
            Dockerfile
            Dockerfile.claude
            nginx.conf
            entrypoint.sh
            deploy-production.bat
            init-project.py
            .env
            .env.*
            *.md
            *.bat
            *.ps1
            *.sh
            *.db
            *.sqlite

      - name: Restart Passenger App
        uses: SamKirkland/FTP-Deploy-Action@v4.3.5
        with:
          server: ${{ secrets.FTP_SERVER }}
          username: ${{ secrets.FTP_USERNAME }}
          password: ${{ secrets.FTP_PASSWORD }}
          server-dir: /public_html/app/tmp/
          protocol: ftps
          port: 21
          local-dir: .github/restart/

Restart Trigger File

Create .github/restart/restart.txt in your repository with any content (e.g., a single space). This file gets uploaded to tmp/restart.txt on every deploy, triggering Passenger to restart the Python app.

.github/
├── restart/
│   └── restart.txt    ← Uploaded to tmp/ on each deploy
└── workflows/
    └── deploy.yml     ← GitHub Actions workflow

Configuration Notes

OptionValueWhy
protocol: ftpsFTPS (explicit TLS on port 21)Encrypted transfer; most cPanel hosts support this
server-dirMust end with /FTP-Deploy-Action requirement
excludeDev-only filesKeeps production clean; .env excluded because it's managed on server

If your host doesn't support FTPS, change protocol: ftps to protocol: ftp. Check cPanel → FTP AccountsConfigure FTP Client for supported protocols.


Files Deployed vs. Excluded

Deployed to Production

public_html/app/
├── main.py                    ← FastAPI application
├── config.py                  ← Configuration module
├── models.py                  ← Data models
├── requirements.txt           ← Python dependencies
├── passenger_wsgi.py          ← Passenger entry point
├── navigation.json            ← Navigation config
├── frontend/
│   ├── index.html
│   ├── login.html
│   ├── components.html
│   ├── css/                   ← All stylesheets
│   ├── js/                    ← All JavaScript modules
│   ├── assets/                ← Fonts, images
│   └── layouts/               ← Layout demo pages
├── tmp/
│   └── restart.txt            ← Passenger restart trigger
└── .env                       ← Production config (managed separately)

Excluded from Deployment

PathReason
.claude/, .claude-flow/, .hive-mind/Dev tooling
_TEMP/, docs/Documentation only
venv/, __pycache__/Local Python artifacts
scripts/, templates/Dev scripts
docker-compose.yml, Dockerfile*, nginx.confDocker setup (not for cPanel)
.env, .env.*Credentials — managed via File Manager
deploy-production.batLegacy manual deploy script
*.mdREADME, CLAUDE.md, etc.

Environment Configuration

Production .env (on cPanel)

Managed via cPanel File Manager. Never committed to Git.

When you need to update environment variables:

  1. Open cPanel → File Manager
  2. Navigate to public_html/app/.env
  3. Right-click → Edit
  4. Make changes, save
  5. Touch tmp/restart.txt (edit and save it) to restart the app

Frontend API URL

The frontend config.js currently hardcodes API_BASE_URL to localhost:8668. For production, update frontend/js/config.js to detect the environment:

javascript
export const CONFIG = {
  API_BASE_URL: window.location.hostname === 'localhost'
    ? 'http://127.0.0.1:8668'
    : `${window.location.origin}/app`,
  // ... rest of config
};

CORS Origins

Update config.py CORS_ORIGINS to include your production domain:

python
CORS_ORIGINS = [
    "http://localhost:3663",
    "http://localhost:8668",
    "http://127.0.0.1:3663",
    "http://127.0.0.1:8668",
    "https://yourdomain.com",
]

Or load from .env:

python
CORS_ORIGINS = os.getenv("CORS_ORIGINS", "http://localhost:3663").split(",")

Day-to-Day Deployment

Standard Workflow

bash
# 1. Make changes locally, test at localhost:3663

# 2. Stage and commit
git add main.py frontend/css/styles.css
git commit -m "Fix mobile layout overflow"

# 3. Push to main — GitHub Actions deploys automatically
git push origin main

# 4. Monitor deployment
#    Go to GitHub → Actions tab → watch the workflow run

# 5. Verify live site
#    Visit https://yourdomain.com/app
#    Check https://yourdomain.com/app/health

When requirements.txt Changes

FTP can upload the file but can't run pip install. When you add/update Python dependencies:

  1. Push the updated requirements.txt (auto-deployed via FTP)
  2. Go to cPanel → Terminal
  3. Run:
bash
source /home/username/virtualenv/public_html/app/3.10/bin/activate
cd ~/public_html/app
pip install -r requirements.txt
  1. Touch tmp/restart.txt in File Manager to restart

Rollback

bash
# Revert the last commit and push — GitHub Actions auto-deploys the revert
git revert HEAD
git push origin main

Method 2: Re-run Previous Deployment

  1. Go to GitHub → Actions tab
  2. Find the last successful workflow run
  3. Click Re-run all jobs

Method 3: Manual Restore

  1. Download the previous version from GitHub (Code → Download ZIP from a specific commit)
  2. Upload files manually via cPanel → File Manager
  3. Touch tmp/restart.txt to restart

Post-Deployment Checklist

After each deployment, verify:

  • [ ] GitHub Actions workflow completed (green checkmark)
  • [ ] Frontend loads: https://yourdomain.com/app/frontend/
  • [ ] Health endpoint responds: https://yourdomain.com/app/health
  • [ ] API docs accessible: https://yourdomain.com/app/docs
  • [ ] Login page renders correctly
  • [ ] Theme toggle (light/dark) works
  • [ ] Mobile layout displays properly
  • [ ] No browser console errors

Troubleshooting

GitHub Actions Workflow Fails

SymptomCauseFix
"Connection refused"Wrong FTP_SERVER secretCheck cPanel → FTP Accounts → Configure FTP Client for correct hostname
"Login incorrect"Wrong FTP_USERNAME or FTP_PASSWORDVerify credentials; try logging in with an FTP client like FileZilla first
"SSL handshake failed"Host doesn't support FTPSChange protocol: ftps to protocol: ftp in workflow
"Could not create directory"FTP user lacks permissionsEnsure FTP account directory is set to public_html/app with write access

Site Not Updating After Deploy

SymptomCauseFix
Old content showingBrowser cacheHard refresh: Ctrl+Shift+R
Python changes not appliedPassenger not restartedEdit tmp/restart.txt via File Manager and save
New files missingFile excluded by deploy configCheck the exclude list in deploy.yml
CSS not loadingPath issueVerify CSS file paths use correct relative paths for subdirectory deployment

Python App Errors

SymptomCauseFix
500 Internal Server ErrorPython exceptionCheck cPanel → MetricsErrors for stack trace
"Module not found"Missing dependencyRun pip install -r requirements.txt in cPanel Terminal
Database connection failedWrong credentials in .envVerify DATABASE_URL in .env via File Manager
App not startingBad passenger_wsgi.pyVerify file exists and imports correctly; check error logs

FTP Connection Issues

If FTPS doesn't work, try these protocols in order:

  1. ftps (port 21) — explicit TLS, most secure
  2. ftp (port 21) — unencrypted, works everywhere
  3. Contact host support to confirm which FTP protocols are enabled

Revision History

VersionDateChanges
2.0February 2026Full rewrite: GitHub Actions + FTP deployment, Python/FastAPI stack, no SSH
1.0February 2026Initial documentation (PHP/MySQL, SSH-based — deprecated)
lock

Enter PIN to continue