Skip to main content

Architecture

TaskForge API runs on the following Azure infrastructure:

Azure App Service

Linux plan, Python 3.11 runtime. Gunicorn serves the Flask app with 4 workers.

Azure SQL Database

Managed SQL Server instance. Schema initialized via scripts/init_db_azure.sql.

GitHub Actions

Two workflows: deployment pipeline and code quality analysis run on every push.
Production URL: https://task-forge-gbd6h8gtg8hchve9.chilecentral-01.azurewebsites.net

Prerequisites

  • An active Azure subscription
  • The Azure CLI installed and authenticated
  • A GitHub repository containing the TaskForge API source code

Deployment procedure

1

Create Azure SQL Database

Create a new Azure SQL Server and database using the Azure portal or CLI:
az sql server create \
  --name <server-name> \
  --resource-group <resource-group> \
  --location <location> \
  --admin-user <admin-user> \
  --admin-password <admin-password>

az sql db create \
  --resource-group <resource-group> \
  --server <server-name> \
  --name taskforge_db
Then run scripts/init_db_azure.sql against the new database in Azure Data Studio or SQL Server Management Studio to initialize the schema (tables, roles, indexes).
2

Create Azure App Service

Create a Linux App Service plan and web app with the Python 3.11 runtime:
az appservice plan create \
  --name taskforge-plan \
  --resource-group <resource-group> \
  --sku B1 \
  --is-linux

az webapp create \
  --resource-group <resource-group> \
  --plan taskforge-plan \
  --name task-forge \
  --runtime "PYTHON:3.11"
3

Configure the startup command

Set the startup command so Azure App Service uses the included startup.sh script:
az webapp config set \
  --resource-group <resource-group> \
  --name task-forge \
  --startup-file "bash startup.sh"
The startup script initializes the database and launches Gunicorn:
startup.sh
#!/bin/bash

# Startup script for Azure App Service
# This script initializes the database and starts the Flask application with Gunicorn

echo "Starting TaskForge API..."

# Set Python path
export PYTHONPATH=/home/site/wwwroot:$PYTHONPATH

# Create logs directory if it doesn't exist
mkdir -p /home/site/wwwroot/logs

# Initialize database (run migrations if needed)
echo "Initializing database..."
python -c "from app import create_app, db; app = create_app('production'); app.app_context().push(); db.create_all()" || echo "Database initialization failed or already initialized"

# Start Gunicorn with production settings
echo "Starting Gunicorn..."
gunicorn --bind=0.0.0.0:8000 \
         --workers=4 \
         --timeout=120 \
         --access-logfile /home/site/wwwroot/logs/access.log \
         --error-logfile /home/site/wwwroot/logs/error.log \
         --log-level info \
         "run:app"
4

Set environment variables on App Service

Configure application settings in Azure App Service. These are exposed as environment variables at runtime:
az webapp config appsettings set \
  --resource-group <resource-group> \
  --name task-forge \
  --settings \
    FLASK_ENV=production \
    SECRET_KEY=<random-32-byte-hex> \
    JWT_SECRET_KEY=<random-32-byte-hex> \
    AZURE_SQL_SERVER=<server>.database.windows.net \
    AZURE_SQL_DATABASE=taskforge_db \
    AZURE_SQL_USER=<admin-user> \
    AZURE_SQL_PASSWORD=<admin-password> \
    AZURE_SQL_PORT=1433
Generate SECRET_KEY and JWT_SECRET_KEY with python -c "import secrets; print(secrets.token_hex(32))". Never reuse or commit these values.
5

Add GitHub secrets

The deployment workflow authenticates to Azure using OIDC (no stored credentials). Add these secrets to your GitHub repository under Settings → Secrets and variables → Actions:
SecretDescription
AZUREAPPSERVICE_CLIENTID_*Azure AD application (client) ID for the federated identity
AZUREAPPSERVICE_TENANTID_*Azure AD tenant ID
AZUREAPPSERVICE_SUBSCRIPTIONID_*Azure subscription ID
SONAR_TOKENSonarCloud project token for the code quality workflow
The trailing * in the secret names is a hash suffix generated by Azure when you set up deployment center — use the exact names shown in the portal.
6

Push to main and verify

Push to the main or master branch. The main_task-forge.yml workflow triggers automatically:
Push → Build → Test (>70% coverage) → Deploy → Azure App Service
Monitor the run in GitHub → Actions. A failed coverage gate blocks deployment.

CI/CD pipeline

Two separate GitHub Actions workflows run on each push:

main_task-forge.yml — Build, test, and deploy

Triggered on push to main or master.
JobWhat it does
buildSets up Python 3.11, installs dependencies, creates release.zip (excludes .env, tests/, htmlcov/), uploads artifact
testRuns pytest --cov=app --cov-fail-under=70 — the pipeline fails if coverage drops below 70%
deployDownloads the build artifact, authenticates to Azure via OIDC, deploys to the Production slot of the task-forge App Service

code-quality.yml — SonarCloud and accessibility

Triggered on push and pull requests to main, master, and develop.
JobWhat it does
sonarcloudRuns pytest with coverage, then uploads results to SonarCloud for bug, vulnerability, code smell, and duplication analysis
accessibilityStarts the Flask dev server, runs AXE-core accessibility tests against the Swagger UI via Playwright/Chromium, uploads an HTML report
The code quality workflow does not block deployment. It runs in parallel and its results are visible in the SonarCloud dashboard and as GitHub Actions artifacts.

Gunicorn configuration

Gunicorn is configured in startup.sh with the following settings:
OptionValueNotes
--bind0.0.0.0:8000Azure App Service routes traffic to port 8000 on Linux
--workers4Pre-fork worker processes
--timeout120Worker timeout in seconds
--access-logfile/home/site/wwwroot/logs/access.logPersistent log storage
--error-logfile/home/site/wwwroot/logs/error.logPersistent log storage

Build docs developers (and LLMs) love