Skip to content

Deploy Python Applications

Framework-specific guide for deploying Python apps to Airbase

This guide covers Python-specific deployment patterns, best practices, and common issues.


Quick Start

Complete example available: Python Streamlit Example

Basic workflow:

# 1. Create Dockerfile
# 2. Build container
airbase container build

# 3. Deploy
airbase container deploy --yes staging

Time: 5 minutes


Dockerfile Pattern for Python

Standard Pattern

Pattern:

FROM gdssingapore/airbase:python-3.13

# Set environment variables
ENV PYTHONUNBUFFERED=TRUE

# Set working directory
WORKDIR /app

# Copy requirements and install dependencies
COPY --chown=app:app requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY --chown=app:app . ./

# Switch to non-root user
USER app

# Expose port
EXPOSE 3000

# Start application
CMD ["python", "app.py"]

Key points: - ✅ PYTHONUNBUFFERED=TRUE - See logs immediately - ✅ --no-cache-dir - Smaller image size - ✅ Install dependencies before copying code (layer caching) - ✅ USER app for security


Required Configuration

1. Bind to 0.0.0.0

Flask:

import os

if __name__ == '__main__':
    app.run(
        host='0.0.0.0',
        port=int(os.environ.get('PORT', 3000))
    )

FastAPI (with uvicorn):

import os
import uvicorn

if __name__ == '__main__':
    uvicorn.run(
        app,
        host='0.0.0.0',
        port=int(os.environ.get('PORT', 3000))
    )

Streamlit:

# In Dockerfile CMD
CMD ["sh", "-c", "streamlit run app.py --server.port=${PORT:-8501} --server.address=0.0.0.0 --server.headless=true"]

2. Read PORT from Environment

import os

PORT = int(os.environ.get('PORT', 3000))

3. Health Check Endpoint

Flask:

@app.route('/health')
def health():
    return {'status': 'ok'}, 200

FastAPI:

@app.get('/health')
def health():
    return {'status': 'ok'}


Common Frameworks

Flask

requirements.txt:

Flask==3.0.2

app.py:

from flask import Flask
import os

app = Flask(__name__)
PORT = int(os.environ.get('PORT', 3000))

@app.route('/health')
def health():
    return {'status': 'ok'}

@app.route('/')
def index():
    return {'message': 'Hello from Flask on Airbase'}

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=PORT)

Dockerfile CMD:

CMD ["python", "app.py"]

FastAPI

requirements.txt:

fastapi==0.109.2
uvicorn==0.27.1

app.py:

from fastapi import FastAPI
import os
import uvicorn

app = FastAPI()
PORT = int(os.environ.get('PORT', 3000))

@app.get('/health')
def health():
    return {'status': 'ok'}

@app.get('/')
def index():
    return {'message': 'Hello from FastAPI on Airbase'}

if __name__ == '__main__':
    uvicorn.run(app, host='0.0.0.0', port=PORT)

Dockerfile CMD:

CMD ["python", "app.py"]

Or using uvicorn directly:

CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "3000"]

Streamlit

See: Python Streamlit Example for complete working code

requirements.txt:

streamlit==1.31.0
pandas==2.2.0
plotly==5.18.0

Dockerfile CMD:

CMD ["sh", "-c", "streamlit run app.py --server.port=${PORT:-8501} --server.address=0.0.0.0 --server.headless=true"]

Instance type: b.small recommended (Streamlit is memory-intensive)


Common Issues

Issue: "ModuleNotFoundError"

Cause: Package not in requirements.txt or not installed

Solution: - Add missing package to requirements.txt - Rebuild container: airbase container build - Verify requirements.txt syntax (no typos)

Issue: "Address already in use"

Cause: Not binding to 0.0.0.0

Solution:

# ❌ Wrong
app.run(host='localhost', port=PORT)

# ✅ Correct
app.run(host='0.0.0.0', port=PORT)

Issue: "Permission denied"

Cause: Files owned by root

Solution: - Use --chown=app:app in COPY commands - Ensure USER app before CMD

Issue: No logs appearing

Cause: Python buffering output

Solution:

ENV PYTHONUNBUFFERED=TRUE

Issue: Slow startup

Cause: Large dependencies or Streamlit cold start

Solution: - Upgrade to b.small instance type - Use --no-cache-dir in pip install - Consider adding warmup endpoint


Best Practices

1. Pin Dependency Versions

Bad:

flask
pandas

Good:

Flask==3.0.2
pandas==2.2.0

Best (with hashes):

pip freeze > requirements.txt

2. Use Virtual Environments Locally

# Create virtual environment
python -m venv venv

# Activate
source venv/bin/activate  # macOS/Linux
venv\Scripts\activate     # Windows

# Install dependencies
pip install -r requirements.txt

# Generate requirements
pip freeze > requirements.txt

3. Minimize Docker Image Size

Use --no-cache-dir:

RUN pip install --no-cache-dir -r requirements.txt

Install only what you need:

# ❌ Don't install entire scipy if you only need numpy
scipy==1.12.0

# ✅ Install just what you need
numpy==1.26.4

4. Security Scanning

Check for vulnerabilities:

pip install safety
safety check -r requirements.txt

5. Environment Variables

import os

# ❌ Bad - hardcoded
DATABASE_URL = "postgres://localhost/mydb"

# ✅ Good - from environment
DATABASE_URL = os.environ.get('DATABASE_URL')

# ✅ Better - with validation
DATABASE_URL = os.environ.get('DATABASE_URL')
if not DATABASE_URL:
    raise ValueError("DATABASE_URL environment variable is required")

Production WSGI Server

For Flask/FastAPI APIs

Use Gunicorn for production:

requirements.txt:

Flask==3.0.2
gunicorn==21.2.0

Dockerfile CMD:

CMD ["gunicorn", "--bind", "0.0.0.0:3000", "--workers", "2", "app:app"]

Why Gunicorn? - ✅ Production-ready - ✅ Multi-worker support - ✅ Better performance than Flask dev server

Configuration:

# gunicorn.conf.py
import os

bind = f"0.0.0.0:{os.environ.get('PORT', 3000)}"
workers = 2
worker_class = "sync"
timeout = 30

Dockerfile CMD with config:

CMD ["gunicorn", "--config", "gunicorn.conf.py", "app:app"]


CSP Considerations

Flask/FastAPI APIs

Good news: APIs serving JSON are CSP-compliant by default

If serving HTML:

from flask import Flask, render_template

@app.route('/page')
def page():
    # ✅ Use templates with external JS
    return render_template('page.html')

page.html:

<!DOCTYPE html>
<html>
<head>
    <title>My Page</title>
</head>
<body>
    <div id="app"></div>
    <!-- ✅ External script (CSP-compliant) -->
    <script src="{{ url_for('static', filename='app.js') }}"></script>
</body>
</html>

Streamlit CSP

See: Python Streamlit Example for CSP-compliant charting with Plotly

Use CSP-compliant libraries: - ✅ Plotly (CSP-compliant) - ❌ Matplotlib inline display (not CSP-compliant) - ❌ Some Streamlit components (check before using)


Performance Optimization

1. Use Appropriate Instance Type

For APIs: nano usually sufficient

For Streamlit: b.small recommended

{
  "instanceType": "b.small"
}

2. Caching in Streamlit

import streamlit as st

@st.cache_data
def load_data():
    # This result is cached
    return expensive_operation()

3. Database Connection Pooling

from sqlalchemy import create_engine
from sqlalchemy.pool import QueuePool

engine = create_engine(
    DATABASE_URL,
    poolclass=QueuePool,
    pool_size=5,
    max_overflow=10
)

Checklist

Before deploying:

  • requirements.txt with pinned versions
  • PYTHONUNBUFFERED=TRUE in Dockerfile
  • App binds to 0.0.0.0
  • PORT read from environment variable
  • Health check endpoint implemented
  • Using --no-cache-dir for pip install
  • All files owned by app:app
  • USER app before CMD
  • No hardcoded secrets in code
  • Using Gunicorn for production (APIs)

See Also