Deploying Flask with Laravel Forge + GitHub Actions
This guide walks you through deploying a production-ready Flask application using Laravel Forge, GitHub Actions, and Supervisor — with automatic database migrations, SSH deployment, and secure environment variable management. We’ll treat this as a fresh project with new migrations. 1.Provision Your Server In Laravel Forge: Create a new server (Ubuntu 22+ recommended) Enable SSH access for your GitHub Actions Add a site (e.g., api.yourapp.com) Clone your Git repo (use "Deploy Script" toggle OFF for now) On the server: ssh forge@your-server-ip cd /home/forge/api.yourapp.com python3 -m venv venv source venv/bin/activate pip install -r requirements.txt 2.Project Structure & Entry Point Your main app should have a run.py file: from app import create_app app = create_app() if __name__ == '__main__': app.run() Use Flask-Migrate and SQLAlchemy in app/init.py: from flask_migrate import Migrate from flask_sqlalchemy import SQLAlchemy from dotenv import load_dotenv import os load_dotenv() db = SQLAlchemy() migrate = Migrate() def create_app(): app = Flask(__name__) app.config.from_object('config.Config') db.init_app(app) migrate.init_app(app, db) return app GitHub Actions Deployment Create .github/workflows/deploy.yml: name: Deploy to Forge on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Deploy over SSH uses: appleboy/ssh-action@v0.1.6 with: host: ${{ secrets.FORGE_HOST }} username: forge key: ${{ secrets.FORGE_SSH_KEY }} script: | cd /home/forge/api.yourapp.com git pull origin main source venv/bin/activate pip install -r requirements.txt flask db upgrade sudo supervisorctl restart flask-app Important: Add the Forge server's SSH private key (id_rsa) to GitHub Secrets as FORGE_SSH_KEY. Managing Environment Variables On the Forge server: Add a .env file in your project root (/home/forge/api.yourapp.com/.env): FLASK_ENV=production SECRET_KEY=supersecret DATABASE_URL=mysql+pymysql://user:pass@host:port/db MAILGUN_DOMAIN=mg.yourapp.com MAILGUN_API_KEY=your-mailgun-key In app/init.py: from dotenv import load_dotenv load_dotenv(dotenv_path='/home/forge/api.yourapp.com/.env') Confirm values load with: flask shell >>> import os >>> os.getenv("MAILGUN_DOMAIN") Running Migrations on Deploy When you push code with new migrations: flask db migrate -m "Add new table" git add migrations/ git commit -m "Add new table" git push GitHub Actions will: Pull the latest code Install dependencies Run flask db upgrade live Restart Gunicorn Supervisor Configuration Set up supervisor to run your Flask app: sudo nano /etc/supervisor/conf.d/flask-app.conf [program:flask-app] command=/home/forge/api.yourapp.com/venv/bin/gunicorn run:app user=forge directory=/home/forge/api.yourapp.com autostart=true autorestart=true stdout_logfile=/var/log/supervisor/flask-app.log stderr_logfile=/var/log/supervisor/flask-app.err.log environment=FLASK_ENV="production" Then reload: sudo supervisorctl reread sudo supervisorctl update sudo supervisorctl restart flask-app You Did It You now have a full CI/CD pipeline: GitHub commit → GitHub Actions → SSH to Forge → Pull, Migrate, Restart New tables are deployed automatically Environment is secure

This guide walks you through deploying a production-ready Flask application using Laravel Forge, GitHub Actions, and Supervisor — with automatic database migrations, SSH deployment, and secure environment variable management.
We’ll treat this as a fresh project with new migrations.
1.Provision Your Server
In Laravel Forge:
- Create a new server (Ubuntu 22+ recommended)
- Enable SSH access for your GitHub Actions
- Add a site (e.g., api.yourapp.com)
- Clone your Git repo (use "Deploy Script" toggle OFF for now)
On the server:
ssh forge@your-server-ip
cd /home/forge/api.yourapp.com
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
2.Project Structure & Entry Point
Your main app should have a run.py file:
from app import create_app
app = create_app()
if __name__ == '__main__':
app.run()
Use Flask-Migrate and SQLAlchemy in app/init.py:
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
from dotenv import load_dotenv
import os
load_dotenv()
db = SQLAlchemy()
migrate = Migrate()
def create_app():
app = Flask(__name__)
app.config.from_object('config.Config')
db.init_app(app)
migrate.init_app(app, db)
return app
- GitHub Actions Deployment
Create .github/workflows/deploy.yml:
name: Deploy to Forge
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Deploy over SSH
uses: appleboy/ssh-action@v0.1.6
with:
host: ${{ secrets.FORGE_HOST }}
username: forge
key: ${{ secrets.FORGE_SSH_KEY }}
script: |
cd /home/forge/api.yourapp.com
git pull origin main
source venv/bin/activate
pip install -r requirements.txt
flask db upgrade
sudo supervisorctl restart flask-app
Important: Add the Forge server's SSH private key (id_rsa) to GitHub Secrets as FORGE_SSH_KEY.
- Managing Environment Variables
On the Forge server:
Add a .env file in your project root (/home/forge/api.yourapp.com/.env):
FLASK_ENV=production
SECRET_KEY=supersecret
DATABASE_URL=mysql+pymysql://user:pass@host:port/db
MAILGUN_DOMAIN=mg.yourapp.com
MAILGUN_API_KEY=your-mailgun-key
In app/init.py:
from dotenv import load_dotenv
load_dotenv(dotenv_path='/home/forge/api.yourapp.com/.env')
Confirm values load with:
flask shell
>>> import os
>>> os.getenv("MAILGUN_DOMAIN")
- Running Migrations on Deploy
When you push code with new migrations:
flask db migrate -m "Add new table"
git add migrations/
git commit -m "Add new table"
git push
GitHub Actions will:
- Pull the latest code
- Install dependencies
- Run flask db upgrade live
- Restart Gunicorn
- Supervisor Configuration
Set up supervisor to run your Flask app:
sudo nano /etc/supervisor/conf.d/flask-app.conf
[program:flask-app]
command=/home/forge/api.yourapp.com/venv/bin/gunicorn run:app
user=forge
directory=/home/forge/api.yourapp.com
autostart=true
autorestart=true
stdout_logfile=/var/log/supervisor/flask-app.log
stderr_logfile=/var/log/supervisor/flask-app.err.log
environment=FLASK_ENV="production"
Then reload:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl restart flask-app
You Did It
You now have a full CI/CD pipeline:
GitHub commit → GitHub Actions → SSH to Forge → Pull, Migrate, Restart
New tables are deployed automatically
Environment is secure