Skip to content

Deploying the Dashboard to Cloud Run

The ScamShield AI dashboard is a Streamlit application deployed on Google Cloud Run. It provides session monitoring, evidence browsing, analytics, and interactive testing.

Stack Overview

Component Technology
Framework Streamlit
Runtime Cloud Run (managed)
Container Python 3.11-slim Docker image
Auth 6-digit PIN with HMAC-signed cookie + Firestore lockout
Proxy Cloudflare Worker (optional, for custom domain)

Prerequisites

  • Google Cloud CLI authenticated to your project
  • Docker (for local testing only; Cloud Run builds from source)
  • Secrets stored in GCP Secret Manager (see step 2)

1. Project Structure

dashboard/
  app.py                  # Home page and navigation hub
  Dockerfile              # Container definition
  entrypoint.sh           # Startup script: env vars -> secrets.toml
  requirements.txt        # Python dependencies
  .streamlit/
    config.toml           # Streamlit configuration (committed)
    secrets.toml          # Local dev secrets (gitignored)
  pages/
    00_testing.py          # Interactive chat testing
    01_live_sessions.py    # Real-time session feed
    02_evidence.py         # Evidence search and filtering
    03_analytics.py        # Session funnel and charts
    04_intelligence.py     # Network graph visualization
    ...
  utils/
    auth.py               # PIN auth with HMAC cookies
    display.py             # Shared rendering functions
    firestore_client.py    # All Firestore read operations

2. Store Secrets in Secret Manager

The dashboard requires two secrets:

# Dashboard login PIN (6 digits)
echo -n "123456" | gcloud secrets create DASHBOARD_PIN \
  --data-file=- \
  --project=your-gcp-project-id

# API key for calling the honeypot endpoint from the Testing page
echo -n "your-api-key-here" | gcloud secrets create SCAMSHIELD_API_KEY \
  --data-file=- \
  --project=your-gcp-project-id

If secrets already exist, create a new version:

echo -n "new-value" | gcloud secrets versions add DASHBOARD_PIN --data-file=-

3. How entrypoint.sh Works

Cloud Run injects secrets as environment variables. The entrypoint.sh script converts them to Streamlit's secrets.toml format at container startup:

#!/bin/sh
mkdir -p /app/.streamlit

cat > /app/.streamlit/secrets.toml <<EOF
DASHBOARD_PIN = "${DASHBOARD_PIN}"

[api]
SCAMSHIELD_API_KEY = "${SCAMSHIELD_API_KEY}"
EOF

exec streamlit run app.py --server.port=8080 --server.address=0.0.0.0 \
  --server.fileWatcherType=none --client.showErrorDetails=false \
  --browser.gatherUsageStats=false

This approach avoids baking secrets into the Docker image.

4. Deploy to Cloud Run

gcloud run deploy scamshield-dashboard \
  --source dashboard/ \
  --project your-gcp-project-id \
  --region asia-south1 \
  --allow-unauthenticated \
  --service-account firebase-deploy@your-gcp-project-id.iam.gserviceaccount.com \
  --memory 512Mi \
  --cpu 1 \
  --min-instances 0 \
  --max-instances 2 \
  --set-secrets="DASHBOARD_PIN=DASHBOARD_PIN:latest,SCAMSHIELD_API_KEY=SCAMSHIELD_API_KEY:latest"

Flag breakdown:

Flag Purpose
--source dashboard/ Build from the dashboard/ directory (Cloud Build handles Docker)
--allow-unauthenticated Public access (PIN auth is handled in-app)
--memory 512Mi Sufficient for Streamlit + Firestore reads
--min-instances 0 Scale to zero when idle (cost savings)
--max-instances 2 Cap scaling (dashboard is low-traffic)
--set-secrets Map Secret Manager secrets to environment variables

5. Health Checks and Scaling

Cloud Run automatically health-checks the container on port 8080. The Streamlit server responds to HTTP requests on / once started.

Scaling behavior:

  • Min instances: 0 -- The dashboard scales to zero when no one is using it. First request after idle incurs a cold start (5-10 seconds for Streamlit).
  • Max instances: 2 -- Caps cost. The dashboard is typically used by 1-2 operators.
  • Concurrency: default (80) -- Streamlit is single-threaded per session, but Cloud Run handles multiple connections to the same instance.

6. PIN Auth Setup

The dashboard uses a 6-digit PIN stored in DASHBOARD_PIN:

  1. User enters PIN on the login screen
  2. PIN is verified against st.secrets["DASHBOARD_PIN"]
  3. On success, an HMAC-signed cookie (scamshield_auth) is set with a 1-hour TTL
  4. Subsequent requests validate the cookie without re-entering the PIN
  5. After 5 failed attempts, Firestore-backed lockout blocks login for 15 minutes

Auth flow in code (utils/auth.py):

Request arrives
  → Check st.session_state (already authed this session?)
    → Check cookie (HMAC-signed, 1-hour TTL)
      → Show PIN login screen
        → On failure: increment lockout counter
        → On success: set cookie + session_state

Every dashboard page calls require_auth() immediately after st.set_page_config():

st.set_page_config(page_title="Evidence", page_icon="...", layout="wide")
require_auth()  # Must be called here -- no exceptions

7. Custom Domain Setup (Optional)

To serve the dashboard on a custom domain, you have two options:

Option A: Cloud Run Domain Mapping

gcloud run domain-mappings create \
  --service scamshield-dashboard \
  --domain dashboard.example.com \
  --region asia-south1

Follow the DNS verification instructions printed by the command.

Option B: Cloudflare Worker Proxy

See Cloudflare Proxy Setup for the recommended approach using a Cloudflare Worker. This provides SSL, caching, and DDoS protection.

8. Local Development

cd dashboard/

# Create local secrets file
cat > .streamlit/secrets.toml <<EOF
DASHBOARD_PIN = "123456"

[api]
SCAMSHIELD_API_KEY = "your-local-api-key"
EOF

# Install dependencies
pip install -r requirements.txt

# Run
streamlit run app.py

The dashboard reads Firestore using Application Default Credentials. Make sure you are authenticated:

gcloud auth application-default login

Troubleshooting

Dashboard Shows Blank Page

If the dashboard loads but renders nothing:

  1. Check Cloudflare Rocket Loader -- Must be OFF (see Cloudflare Proxy)
  2. Check browser console for JavaScript errors
  3. Verify the container started correctly:
    gcloud run services logs read scamshield-dashboard \
      --region asia-south1 --limit 20
    

Secrets Not Available

If st.secrets["DASHBOARD_PIN"] throws KeyError:

  1. Verify entrypoint.sh runs before Streamlit (check the CMD in Dockerfile)
  2. Verify secrets are mapped in Cloud Run:
    gcloud run services describe scamshield-dashboard \
      --region asia-south1 --format="yaml(spec.template.spec.containers[0].env)"
    
  3. Verify secrets exist in Secret Manager:
    gcloud secrets list --project=your-gcp-project-id
    

The HMAC cookie key is derived from SHA256(salt + PIN). Changing the PIN invalidates all existing cookie sessions. This is by design.

If cookies are not persisting across page loads, check that:

  • The extra-streamlit-components package is installed
  • The browser allows cookies from the dashboard domain
  • No proxy is stripping Set-Cookie headers