Skip to content

Python Streamlit Example

Complete example: Deploy a Streamlit application to Airbase

This example shows how to build and deploy a Streamlit data visualization application to Airbase, including handling CSP considerations.


Overview

What we're building: - Interactive data dashboard using Streamlit - Data visualization with charts - User input widgets - CSV data upload

Tech stack: - Python 3.13 - Streamlit - Pandas - Plotly (CSP-compatible charting library)

Deployment time: ~5 minutes


Project Structure

streamlit-dashboard/
├── app.py                    # Main Streamlit application
├── requirements.txt          # Python dependencies
├── Dockerfile               # Container configuration
├── airbase.json             # Airbase configuration
├── .env                     # Environment variables (optional)
└── .gitignore              # Git ignore file

Step 1: Create Streamlit Application

app.py:

import streamlit as st
import pandas as pd
import plotly.express as px
import os

# Read port from environment (Airbase requirement)
PORT = int(os.environ.get('PORT', 8501))

# Page configuration
st.set_page_config(
    page_title="Data Dashboard",
    page_icon="📊",
    layout="wide"
)

# Title
st.title("📊 Interactive Data Dashboard")
st.markdown("---")

# Sidebar
with st.sidebar:
    st.header("Configuration")

    # Data source selection
    data_source = st.radio(
        "Select Data Source:",
        ["Sample Data", "Upload CSV"]
    )

    if data_source == "Upload CSV":
        uploaded_file = st.file_uploader("Choose a CSV file", type="csv")

# Main content
col1, col2 = st.columns(2)

with col1:
    st.subheader("Data Overview")

    # Load data based on selection
    if data_source == "Sample Data":
        # Create sample data
        df = pd.DataFrame({
            'Category': ['A', 'B', 'C', 'D', 'E'],
            'Value': [23, 45, 56, 78, 32],
            'Growth': [12, 18, -5, 23, 8]
        })
        st.success("Using sample data")
    else:
        if uploaded_file is not None:
            df = pd.read_csv(uploaded_file)
            st.success(f"Loaded {len(df)} rows")
        else:
            st.info("Please upload a CSV file")
            df = None

    # Display data
    if df is not None:
        st.dataframe(df, use_container_width=True)

        # Summary statistics
        st.subheader("Summary Statistics")
        st.write(df.describe())

with col2:
    st.subheader("Visualizations")

    if df is not None:
        # Chart type selector
        chart_type = st.selectbox(
            "Select Chart Type:",
            ["Bar Chart", "Line Chart", "Scatter Plot"]
        )

        # Create visualization using Plotly (CSP-compatible)
        if chart_type == "Bar Chart":
            fig = px.bar(
                df,
                x=df.columns[0],
                y=df.columns[1],
                title=f"{df.columns[1]} by {df.columns[0]}"
            )
        elif chart_type == "Line Chart":
            fig = px.line(
                df,
                x=df.columns[0],
                y=df.columns[1],
                title=f"{df.columns[1]} Trend"
            )
        else:  # Scatter Plot
            if len(df.columns) >= 3:
                fig = px.scatter(
                    df,
                    x=df.columns[1],
                    y=df.columns[2],
                    title=f"{df.columns[2]} vs {df.columns[1]}"
                )
            else:
                st.warning("Scatter plot requires at least 3 columns")
                fig = None

        # Display chart
        if fig is not None:
            st.plotly_chart(fig, use_container_width=True)

# Interactive widgets section
st.markdown("---")
st.subheader("Interactive Controls")

col3, col4, col5 = st.columns(3)

with col3:
    slider_value = st.slider("Select a value:", 0, 100, 50)
    st.metric("Selected Value", slider_value)

with col4:
    text_input = st.text_input("Enter text:", "Hello Airbase!")
    st.write(f"You entered: {text_input}")

with col5:
    date_input = st.date_input("Select a date:")
    st.write(f"Selected date: {date_input}")

# Footer
st.markdown("---")
st.caption("Deployed on Airbase 🚀 | Built with Streamlit")

Key features: - ✅ Reads PORT from environment variable - ✅ Uses Plotly (CSP-compatible charting) - ✅ Interactive widgets - ✅ File upload capability - ✅ Responsive layout


Step 2: Create Requirements File

requirements.txt:

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

Why these libraries: - Streamlit: Web framework for data apps - Pandas: Data manipulation - Plotly: CSP-compatible interactive charts

Important: Plotly is CSP-compatible. Avoid libraries like matplotlib inline display or altair with default settings, as they may generate inline scripts.


Step 3: Create Dockerfile

Dockerfile:

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 app.py ./

# Switch to non-root user
USER app

# Expose port (Streamlit default is 8501, but Airbase uses PORT env var)
EXPOSE 8501

# Run Streamlit
# Note: --server.port uses PORT env var, --server.address binds to all interfaces
CMD ["sh", "-c", "streamlit run app.py --server.port=${PORT:-8501} --server.address=0.0.0.0 --server.headless=true"]

Key configuration: - ✅ Uses Airbase Python 3.13 base image - ✅ Runs as non-root user (app) - ✅ Files owned by app:app - ✅ Binds to 0.0.0.0 (required for container networking) - ✅ Reads $PORT from environment - ✅ Headless mode (no browser auto-open)


Step 4: Create Airbase Configuration

airbase.json:

{
  "framework": "container",
  "handle": "your-team/streamlit-dashboard",
  "port": 8501,
  "instanceType": "b.small"
}

Configuration notes: - port: 8501 (Streamlit's default) - instanceType: b.small recommended (Streamlit needs more memory than nano) - handle: Replace with your actual project handle

Why b.small? - Streamlit apps are memory-intensive - b.small provides 1GB RAM (vs 500MB for nano) - Better performance for data processing


Step 5: Create Environment Variables (Optional)

.env:

PORT=8501
STREAMLIT_SERVER_HEADLESS=true
STREAMLIT_SERVER_ENABLE_CORS=false

Optional settings - Streamlit works fine without these, but you can customize behavior.


Step 6: Create .gitignore

.gitignore:

# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
venv/
env/

# Environment files
.env
.env.*

# Streamlit
.streamlit/

# IDE
.vscode/
.idea/

# OS
.DS_Store

Deploy to Airbase

Build and Deploy to Staging

# Navigate to project directory
cd streamlit-dashboard

# Build container
airbase container build

# Deploy to staging for testing
airbase container deploy --yes staging

Expected output:

Building container...
✓ Build complete
✓ Image pushed to registry

Deploying to staging environment...
✓ Deployment successful
✓ Application is live at: https://staging--streamlit-dashboard.app.tc1.airbase.sg

Deployment time: 2-3 minutes

Test in Staging

# Open in browser
open https://staging--streamlit-dashboard.app.tc1.airbase.sg

What to test: - [ ] Dashboard loads correctly - [ ] Charts render properly - [ ] Widgets are interactive - [ ] File upload works - [ ] No CSP errors in browser console (F12)

Deploy to Production

# If staging tests pass, deploy to production
airbase container deploy --yes

Production URL: https://streamlit-dashboard.app.tc1.airbase.sg


CSP Compatibility

Streamlit and CSP

Good news: Core Streamlit is CSP-compatible when used correctly.

What works: - ✅ Streamlit widgets (sliders, inputs, buttons) - ✅ Plotly charts - ✅ Pandas dataframes - ✅ File uploads - ✅ st.columns, st.tabs layouts

What may cause CSP issues: - ❌ Some third-party Streamlit components - ❌ Matplotlib with inline display - ❌ Custom HTML with inline scripts - ❌ Some visualization libraries (Altair default config)

If You Encounter CSP Violations

Step 1: Check browser console

Press F12 and look for errors like:

Refused to execute inline script because it violates the following
Content Security Policy directive: "script-src 'self'".

Step 2: Identify the source

  • Is it from a third-party Streamlit component?
  • Is it from a visualization library?
  • Is it from custom HTML?

Step 3: Find alternatives

  • Use Plotly instead of Matplotlib inline
  • Use built-in Streamlit charts
  • Avoid custom HTML with inline scripts
  • Check if component has CSP-compatible mode

Step 4: Last resort - Nginx proxy workaround

If you absolutely must use a library with CSP violations (e.g., specific Streamlit components for government data visualization):

See: CSP Nginx Workaround (Note: This weakens security and should only be used when no alternatives exist)


Advanced: Multiple Pages

Streamlit supports multi-page apps:

streamlit-dashboard/
├── app.py                      # Home page
├── pages/
│   ├── 1_📈_Analytics.py       # Analytics page
│   ├── 2_📊_Reports.py         # Reports page
│   └── 3_⚙️_Settings.py        # Settings page
├── requirements.txt
├── Dockerfile
└── airbase.json

No code changes needed - Streamlit automatically detects pages in the pages/ directory.

Update Dockerfile to copy pages:

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

Performance Optimization

Caching

Use Streamlit's caching to improve performance:

import streamlit as st

@st.cache_data
def load_data():
    # This function result is cached
    df = pd.read_csv("large_file.csv")
    return df

# Data only loads once, then cached
df = load_data()

Benefits: - Faster page loads - Reduced memory usage - Better user experience

Session State

Persist data across reruns:

# Initialize session state
if 'counter' not in st.session_state:
    st.session_state.counter = 0

# Button increments counter
if st.button('Increment'):
    st.session_state.counter += 1

st.write(f"Counter: {st.session_state.counter}")

Troubleshooting

Issue: App not accessible

Symptom: Deployment succeeds but URL doesn't load

Causes: 1. Wrong port in airbase.json 2. App not binding to 0.0.0.0 3. App crashed on startup

Solution:

Check Dockerfile CMD:

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

Ensure airbase.json port matches:

{
  "port": 8501
}

Issue: Out of memory

Symptom: App crashes or becomes unresponsive

Solution: Upgrade instance type in airbase.json:

Ensure you're using b.small instance type:

{
  "instanceType": "b.small"
}

Then redeploy:

airbase container deploy --yes

Issue: Slow performance

Solutions:

  1. Add caching:

    @st.cache_data
    def expensive_computation():
        # ...
    

  2. Optimize data processing:

  3. Keep datasets small (<100MB)
  4. Use data sampling for large datasets
  5. Avoid loading entire datasets into memory
  6. Use generators for large data processing

  7. Optimize data loading:

  8. Load less data
  9. Filter data server-side
  10. Use data pagination

Issue: CSP violations

Symptom: Charts or components not rendering

Solution:

  1. Check browser console (F12)
  2. Identify violating library
  3. Switch to CSP-compatible alternative:
  4. Use Plotly instead of Matplotlib inline
  5. Use st.plotly_chart() instead of st.pyplot()
  6. Avoid custom HTML with inline scripts

Production Checklist

Before deploying to production:

  • Test all features in staging
  • Check browser console for CSP errors
  • Verify file uploads work (if using)
  • Test with realistic data sizes
  • Check performance under load
  • Ensure proper instance type (b.small recommended)
  • Set up monitoring (check application logs)
  • Document any custom configuration

Next Steps

Enhance your dashboard: - Add authentication (Streamlit supports OAuth) - Connect to databases (PostgreSQL, MongoDB) - Add real-time data updates - Implement user permissions - Create downloadable reports

Resources: - Streamlit Documentation - Plotly Python Documentation - Pandas Documentation


Complete Example Repository

You can find the complete working example at:

streamlit-dashboard/
├── app.py                 # 120 lines
├── requirements.txt       # 3 lines
├── Dockerfile            # 15 lines
├── airbase.json          # 6 lines
└── .gitignore            # 10 lines

Total: ~150 lines of code for a complete interactive dashboard.


See Also