Deploying a Google Earth Engine App on Streamlit Cloud

GEE
Streamlit
Deployment
Python
Cloud
Author

Pulakesh Pradhan

Published

March 5, 2026

A step-by-step guide covering setup, authentication, secrets management, common errors, and deployment โ€” based on real-world debugging experience.

A step-by-step guide covering setup, authentication, secrets management, common errors, and deployment โ€” based on real-world debugging experience.

A step-by-step guide covering setup, authentication, secrets management, common errors, and deployment โ€” based on real-world debugging experience.


Table of Contents

  1. Introduction
  2. Prerequisites
  3. Creating a GEE Service Account
  4. Project Structure
  5. Writing the Streamlit App
  6. Setting Up secrets.toml
  7. Configuring requirements.txt
  8. Deploying to Streamlit Cloud
  9. Configuring Google Cloud IAM Permissions
  10. Common Errors & Solutions
  11. Final Checklist
  12. Resources

1. Introduction

Google Earth Engine (GEE) is a powerful platform for geospatial analysis. Combining it with Streamlit lets you build interactive web apps to visualize satellite imagery, elevation models, and more โ€” all accessible from a browser.

However, deploying a GEE-powered Streamlit app to Streamlit Cloud involves several authentication and permission steps that can be tricky. This guide walks you through the entire process, with special focus on errors youโ€™re likely to encounter and how to fix them.


2. Prerequisites

Before you start, make sure you have:


3. Creating a GEE Service Account

On your local machine, you can use ee.Authenticate() interactively. But on Streamlit Cloud, thereโ€™s no browser โ€” you need a Service Account.

Step 1: Create a GCP Project

  1. Go to Google Cloud Console
  2. Click Select a project โ†’ New Project
  3. Name it (e.g., ee-myproject) and click Create

Step 2: Enable the Earth Engine API

  1. Go to APIs & Services โ†’ Library
  2. Search for โ€œEarth Engine APIโ€
  3. Click Enable

Step 3: Create a Service Account

  1. Go to IAM & Admin โ†’ Service Accounts
  2. Click + CREATE SERVICE ACCOUNT
  3. Give it a name (e.g., my-gee-bot)
  4. Click Create and Continue
  5. Skip the optional steps and click Done

Step 4: Create & Download a JSON Key

  1. Click on your new service account
  2. Go to the Keys tab
  3. Click Add Key โ†’ Create new key
  4. Select JSON and click Create
  5. A .json file will be downloaded โ€” keep this file safe!

The downloaded JSON file looks like this:

{
  "type": "service_account",
  "project_id": "ee-myproject",
  "private_key_id": "abc123def456ghi789jkl012mno345pqr678stu9",
  "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASC...(long key)...\n-----END PRIVATE KEY-----\n",
  "client_email": "my-gee-bot@ee-myproject.iam.gserviceaccount.com",
  "client_id": "123456789012345678901",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/my-gee-bot%40ee-myproject.iam.gserviceaccount.com",
  "universe_domain": "googleapis.com"
}

Step 5: Register the Service Account with Earth Engine

  1. Go to Earth Engine โ†’ Assets
  2. Or visit: https://signup.earthengine.google.com/#!/service_accounts
  3. Register your service account email (e.g., my-gee-bot@ee-myproject.iam.gserviceaccount.com)

4. Project Structure

Your GitHub repository should have this structure:

my-gee-app/
โ”œโ”€โ”€ .streamlit/
โ”‚   โ””โ”€โ”€ secrets.toml       โ† โš ๏ธ DO NOT commit this to GitHub!
โ”œโ”€โ”€ app.py                  โ† Main Streamlit application
โ”œโ”€โ”€ requirements.txt        โ† Python dependencies
โ”œโ”€โ”€ setup.sh                โ† (Optional) Streamlit config
โ””โ”€โ”€ .gitignore              โ† Must include secrets.toml

.gitignore

Make sure your .gitignore includes:

.streamlit/secrets.toml
*.json
__pycache__/

โš ๏ธ NEVER commit your service account JSON or secrets.toml to GitHub. Your private key would be exposed publicly!


5. Writing the Streamlit App

Hereโ€™s a complete working app that displays NASA SRTM elevation data on a Folium map:

import streamlit as st
import ee
import json
import tempfile
import os
import folium
from streamlit_folium import st_folium

# --- Page Config ---
st.set_page_config(layout="wide", page_title="GEE Elevation Explorer")

# --- Earth Engine Authentication ---
try:
    json_key = None
    if 'json_data' in st.secrets:
        json_key = 'json_data'
    elif 'EE_SERVICE_ACCOUNT' in st.secrets:
        json_key = 'EE_SERVICE_ACCOUNT'

    if json_key:
        # For Streamlit Cloud: write JSON to temp file, then authenticate
        service_account_json = st.secrets[json_key]
        key_path = os.path.join(tempfile.gettempdir(), 'ee_service_account.json')
        with open(key_path, 'w') as f:
            f.write(service_account_json)

        sa_email = st.secrets.get(
            'service_account',
            json.load(open(key_path))['client_email']
        )

        credentials = ee.ServiceAccountCredentials(sa_email, key_file=key_path)
        ee.Initialize(credentials, project='ee-myproject')
    else:
        # For local development
        ee.Initialize(project='ee-myproject')

except Exception as e:
    st.error("๐Ÿ”‘ Earth Engine Authentication Failed")
    st.write(f"**Error:** {e}")
    st.stop()

# --- App Content ---
st.title("๐Ÿ›ฐ๏ธ NASA SRTM Elevation Explorer")

lat, lon = 20.4625, 85.8828
dem = ee.Image('USGS/SRTMGL1_003')

vis_params = {
    'min': 0,
    'max': 100,
    'palette': ['006633', 'E5FFCC', '662A00', 'D8D8D8', 'F5F5F5']
}

dem_map_id = dem.getMapId(vis_params)

m = folium.Map(location=[lat, lon], zoom_start=12)

folium.TileLayer(
    tiles=dem_map_id['tile_fetcher'].url_format,
    attr='Google Earth Engine',
    name='SRTM Elevation',
    overlay=True,
    control=True
).add_to(m)

folium.Marker(
    location=[lat, lon],
    popup='Study Location',
    icon=folium.Icon(color='red', icon='info-sign')
).add_to(m)

folium.LayerControl().add_to(m)

st_folium(m, width=None, height=550, use_container_width=True)

๐Ÿ”‘ Key Design Decisions in the Authentication Code

Why use a temp file instead of json.loads()?

# โŒ FRAGILE โ€” breaks with TOML escape characters
sa_info = json.loads(st.secrets['json_data'])
credentials = ee.ServiceAccountCredentials(
    sa_info['client_email'],
    key_data=st.secrets['json_data']
)

# โœ… ROBUST โ€” writes to temp file, avoids all escape issues
service_account_json = st.secrets[json_key]
key_path = os.path.join(tempfile.gettempdir(), 'ee_service_account.json')
with open(key_path, 'w') as f:
    f.write(service_account_json)
credentials = ee.ServiceAccountCredentials(sa_email, key_file=key_path)

The json.loads() approach fails because TOML multi-line strings can mangle the \n escape characters in the private key, causing Invalid \escape errors. The temp file approach completely avoids this.

Why NOT use geemap?

The geemap library is great locally but causes BoxKeyError crashes on Streamlit Cloud due to python-box version conflicts with xyzservices. Using plain folium + ee is much more reliable for deployment.


6. Setting Up secrets.toml

This is the most critical step and where most errors originate.

โœ… Correct Format

The secrets.toml file must use TOML triple-quoted literal strings ('''). Copy the entire content of your downloaded JSON file between the triple quotes:

json_data = '''
{
  "type": "service_account",
  "project_id": "ee-myproject",
  "private_key_id": "abc123def456ghi789jkl012mno345pqr678stu9",
  "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcw...\n...(your full private key here)...\n-----END PRIVATE KEY-----\n",
  "client_email": "my-gee-bot@ee-myproject.iam.gserviceaccount.com",
  "client_id": "123456789012345678901",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/my-gee-bot%40ee-myproject.iam.gserviceaccount.com",
  "universe_domain": "googleapis.com"
}
'''

service_account = 'my-gee-bot@ee-myproject.iam.gserviceaccount.com'

โš ๏ธ Common Mistakes with secrets.toml

โŒ Mistake 1: Using double-quoted TOML strings

# WRONG โ€” double quotes process escape sequences and will break your key
json_data = """
{
  "private_key": "-----BEGIN PRIVATE KEY-----\n..."
}
"""

Fix: Always use single-quoted triple strings ('''), which are TOML literal strings that preserve content exactly as-is.

โŒ Mistake 2: Corrupted private key (missing \n)

If you manually copy-paste the JSON, you might accidentally delete a \n from the private key. For example:

# BROKEN โ€” \S is not a valid JSON escape
...iRPF\So8xuaXk7...

# CORRECT โ€” \n followed by So8x
...iRPF\nSo8xuaXk7...

Fix: Always copy the JSON directly from the downloaded .json file. Donโ€™t hand-edit the private key.

โŒ Mistake 3: Extra whitespace or line breaks inside the JSON

The JSON content between ''' and ''' must be valid JSON. Avoid adding extra line breaks or tabs.

โœ… Best Practice: Generate secrets.toml Programmatically

To avoid copy-paste errors, use this Python script:

# generate_secrets.py
import sys

json_path = sys.argv[1]  # Path to your downloaded .json key file

with open(json_path, 'r') as f:
    json_content = f.read().strip()

# Extract client_email from JSON
import json
data = json.loads(json_content)
email = data['client_email']

toml = f"json_data = '''\n{json_content}\n'''\n\nservice_account = '{email}'\n"

with open('.streamlit/secrets.toml', 'w') as f:
    f.write(toml)

print(f"โœ… secrets.toml generated successfully!")
print(f"   Service account: {email}")

Run it:

python generate_secrets.py path/to/your-service-account-key.json

Where to Put Secrets

Environment Location
Local development .streamlit/secrets.toml file (in your project root)
Streamlit Cloud App Settings โ†’ Secrets (paste the same content)

7. Configuring requirements.txt

Keep it minimal โ€” fewer dependencies = fewer bugs:

streamlit
streamlit-folium
earthengine-api
folium
fpdf

โš ๏ธ Dependency Pitfalls to Avoid

Package Problem Recommendation
geemap Causes BoxKeyError due to python-box conflicts Donโ€™t use โ€” use plain folium + ee
python-box Version conflicts with geemap and xyzservices Donโ€™t install unless absolutely needed
leafmap Heavy dependency, may conflict Remove if not needed

8. Deploying to Streamlit Cloud

Step 1: Push to GitHub

git init
git add app.py requirements.txt setup.sh .gitignore
git commit -m "Initial GEE Streamlit app"
git remote add origin https://github.com/yourusername/your-repo.git
git push -u origin main

โš ๏ธ Do NOT push secrets.toml or .json key files!

Step 2: Deploy on Streamlit Cloud

  1. Go to share.streamlit.io
  2. Click โ€œNew appโ€
  3. Connect your GitHub repo
  4. Set:
    • Repository: yourusername/your-repo
    • Branch: main
    • Main file path: app.py
  5. Click Deploy

Step 3: Add Secrets on Streamlit Cloud

  1. Once deployed, click โ€œManage appโ€ (bottom-right corner)
  2. Click the โ‹ฎ (three dots) โ†’ Settings
  3. Go to the โ€œSecretsโ€ tab
  4. Paste the entire content of your secrets.toml file
  5. Click Save

๐Ÿ’ก Changes to secrets take about 1 minute to propagate. You may need to reboot the app.


9. Configuring Google Cloud IAM Permissions

Even after authentication succeeds, you may get permission errors. The service account needs specific IAM roles on your GCP project.

Required Roles

Go to IAM & Admin โ†’ IAM and grant these roles to your service account:

Role Purpose
Service Usage Consumer Allows calling Google APIs on the project
Earth Engine Resource Writer Allows running Earth Engine computations

How to Grant Roles

  1. Go to console.cloud.google.com/iam-admin/iam?project=YOUR_PROJECT_ID

  2. Click โ€œ+ GRANT ACCESSโ€

  3. In โ€œNew principalsโ€, enter your service account email:

    my-gee-bot@ee-myproject.iam.gserviceaccount.com
  4. Under โ€œSelect a roleโ€, add:

    • Service Usage Consumer
    • Click โ€œ+ ADD ANOTHER ROLEโ€
    • Earth Engine Resource Writer
  5. Click Save

โณ Important: Permission changes can take 2-5 minutes to propagate. Be patient!


10. Common Errors & Solutions

Here are the exact errors youโ€™ll encounter during deployment, in the order they typically appear, and how to fix each one.


Error 1: Earth Engine Authentication Failed

๐Ÿ”‘ Earth Engine Authentication Failed

Error Details: Please authorize access to your Earth Engine account by running
earthengine authenticate
in your command line, or ee.Authenticate() in Python, and then retry.

Cause: The app canโ€™t find the service account credentials.

Fix: - Ensure secrets.toml is correctly formatted (see Section 6) - On Streamlit Cloud: paste secrets in App Settings โ†’ Secrets - Check that json_data key name matches what your code expects - Make sure your code checks st.secrets for the correct key:

if 'json_data' in st.secrets:
    # Use secrets

Error 2: Invalid \escape: line X column Y

Error Details: Invalid \escape: line 5 column 1036 (char 1173)

Cause: The private key in your JSON contains \n sequences for line breaks. If any \n gets corrupted during copy-paste (e.g., \nS becomes \S), json.loads() fails because \S is not a valid JSON escape.

Fix: 1. Donโ€™t use json.loads() โ€” use the temp file approach instead:

# Write the secret string to a file โ€” bypasses JSON escape issues
service_account_json = st.secrets['json_data']
key_path = os.path.join(tempfile.gettempdir(), 'ee_service_account.json')
with open(key_path, 'w') as f:
    f.write(service_account_json)

credentials = ee.ServiceAccountCredentials(sa_email, key_file=key_path)
  1. Regenerate secrets.toml from the original JSON file using the Python script in Section 6.

  2. Verify your JSON is valid before deploying:

# verify_secrets.py
import json

with open('.streamlit/secrets.toml', 'r') as f:
    content = f.read()

marker = "'''"
start = content.index(marker) + 3
end = content.index(marker, start)
json_str = content[start:end].strip()

try:
    data = json.loads(json_str)
    print("โœ… JSON is VALID!")
    print("   client_email:", data['client_email'])
except json.JSONDecodeError as e:
    print("โŒ JSON ERROR:", e)

Error 3: Caller does not have required permission to use project

Error Details: Caller does not have required permission to use project
'projects/ee-myproject'. Grant the caller the
roles/serviceusage.serviceUsageConsumer role...

Cause: Authentication succeeded, but the service account lacks the Service Usage Consumer IAM role.

Fix: 1. Go to IAM & Admin โ†’ IAM 2. Click โ€œ+ GRANT ACCESSโ€ 3. Add your service account email as principal 4. Assign role: Service Usage Consumer 5. Click Save 6. Wait 2-3 minutes, then reboot the app


Error 4: Permission 'earthengine.computations.create' denied

Error Details: Permission 'earthengine.computations.create' denied on
resource 'projects/ee-myproject' (or it may not exist).

Cause: The service account can call Google APIs but doesnโ€™t have Earth Engine-specific permissions.

Fix: 1. Go to IAM & Admin โ†’ IAM 2. Find your service account and click the edit (โœ๏ธ) icon 3. Click โ€œ+ ADD ANOTHER ROLEโ€ 4. Add: Earth Engine Resource Writer 5. Click Save 6. Wait 2-3 minutes, then reboot the app


Error 5: BoxKeyError (geemap + python-box conflict)

box.exceptions.BoxKeyError: This app has encountered an error.

Traceback:
  File "app.py", line 45, in <module>
    import geemap.foliumap as geemap
  File ".../geemap/foliumap.py", line 46, in <module>
    basemaps = box.Box(basemaps.xyz_to_folium(), frozen_box=True)

Cause: geemap uses python-box internally, and version conflicts between python-box, xyzservices, and geemap cause crashes on Streamlit Cloud.

Fix: Donโ€™t use geemap for Streamlit Cloud deployments. Use plain folium + earthengine-api:

# โŒ AVOID
import geemap.foliumap as geemap
m = geemap.Map(center=[lat, lon], zoom=12)
m.addLayer(srtm, vis_params, 'SRTM')

# โœ… USE INSTEAD
import folium
dem_map_id = dem.getMapId(vis_params)
m = folium.Map(location=[lat, lon], zoom_start=12)
folium.TileLayer(
    tiles=dem_map_id['tile_fetcher'].url_format,
    attr='Google Earth Engine',
    name='SRTM Elevation',
    overlay=True,
    control=True
).add_to(m)

Update requirements.txt:

# Remove these:
# geemap
# leafmap
# python-box==6.1.0
# xyzservices

# Keep these:
streamlit
streamlit-folium
earthengine-api
folium
fpdf

Error Troubleshooting Flowchart

App shows error
โ”‚
โ”œโ”€โ”€ "Please authorize access" or "Authentication Failed"
โ”‚   โ””โ”€โ”€ โ†’ Check secrets.toml format & Streamlit Cloud Secrets
โ”‚
โ”œโ”€โ”€ "Invalid \escape"
โ”‚   โ””โ”€โ”€ โ†’ Use temp file approach, regenerate secrets.toml from original JSON
โ”‚
โ”œโ”€โ”€ "does not have required permission" / "serviceUsageConsumer"
โ”‚   โ””โ”€โ”€ โ†’ Add Service Usage Consumer role in GCP IAM
โ”‚
โ”œโ”€โ”€ "earthengine.computations.create denied"
โ”‚   โ””โ”€โ”€ โ†’ Add Earth Engine Resource Writer role in GCP IAM
โ”‚
โ”œโ”€โ”€ "BoxKeyError" / python-box crash
โ”‚   โ””โ”€โ”€ โ†’ Remove geemap, use plain folium + ee
โ”‚
โ””โ”€โ”€ App loads but map is blank
    โ””โ”€โ”€ โ†’ Check if EE API is enabled in GCP Console

11. Final Checklist

Before deploying, verify each item:


12. Resources

Resource Link
Streamlit Cloud share.streamlit.io
Streamlit Secrets Management docs.streamlit.io/deploy/streamlit-community-cloud/deploy-your-app/secrets-management
Google Earth Engine earthengine.google.com
GEE Python API Docs developers.google.com/earth-engine/guides/python_install
GCP IAM Console console.cloud.google.com/iam-admin/iam
GCP Service Accounts console.cloud.google.com/iam-admin/serviceaccounts
Folium Documentation python-visualization.github.io/folium
FPDF Documentation py-pdf.github.io/fpdf2/

About This Guide

This guide was written based on real deployment experience, documenting every error encountered and resolved during the process of deploying a Google Earth Engine SRTM elevation analysis app to Streamlit Cloud. Every error and solution described here was encountered and verified firsthand.

Key Takeaways: 1. Use temp file approach for credentials โ€” never json.loads() on TOML strings 2. Use plain folium โ€” avoid geemap on Streamlit Cloud 3. Donโ€™t forget IAM roles โ€” both Service Usage Consumer AND Earth Engine Resource Writer 4. Generate your secrets.toml programmatically โ€” donโ€™t copy-paste the private key manually


Last updated: March 2026