Deploying a Google Earth Engine App on Streamlit Cloud
A step-by-step guide covering setup, authentication, secrets management, common errors, and deployment โ based on real-world debugging experience.
Table of Contents
- Introduction
- Prerequisites
- Creating a GEE Service Account
- Project Structure
- Writing the Streamlit App
- Setting Up
secrets.toml - Configuring
requirements.txt - Deploying to Streamlit Cloud
- Configuring Google Cloud IAM Permissions
- Common Errors & Solutions
- Final Checklist
- 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:
- โ A Google Cloud Platform (GCP) account
- โ A Google Earth Engine account (sign up at signup.earthengine.google.com)
- โ A GitHub account
- โ A Streamlit Cloud account (free tier available)
- โ Python 3.8+ installed locally
- โ Git installed locally
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
- Go to Google Cloud Console
- Click Select a project โ New Project
- Name it (e.g.,
ee-myproject) and click Create
Step 2: Enable the Earth Engine API
- Go to APIs & Services โ Library
- Search for โEarth Engine APIโ
- Click Enable
Step 3: Create a Service Account
- Go to IAM & Admin โ Service Accounts
- Click + CREATE SERVICE ACCOUNT
- Give it a name (e.g.,
my-gee-bot) - Click Create and Continue
- Skip the optional steps and click Done
Step 4: Create & Download a JSON Key
- Click on your new service account
- Go to the Keys tab
- Click Add Key โ Create new key
- Select JSON and click Create
- A
.jsonfile 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
- Go to Earth Engine โ Assets
- Or visit:
https://signup.earthengine.google.com/#!/service_accounts - 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.tomlto 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.jsonWhere 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.tomlor.jsonkey files!
Step 2: Deploy on Streamlit Cloud
- Go to share.streamlit.io
- Click โNew appโ
- Connect your GitHub repo
- Set:
- Repository:
yourusername/your-repo - Branch:
main - Main file path:
app.py
- Repository:
- Click Deploy
Step 3: Add Secrets on Streamlit Cloud
- Once deployed, click โManage appโ (bottom-right corner)
- Click the โฎ (three dots) โ Settings
- Go to the โSecretsโ tab
- Paste the entire content of your
secrets.tomlfile - 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
Go to console.cloud.google.com/iam-admin/iam?project=YOUR_PROJECT_ID
Click โ+ GRANT ACCESSโ
In โNew principalsโ, enter your service account email:
my-gee-bot@ee-myproject.iam.gserviceaccount.comUnder โSelect a roleโ, add:
Service Usage Consumer- Click โ+ ADD ANOTHER ROLEโ
Earth Engine Resource Writer
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 secretsError 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)Regenerate
secrets.tomlfrom the original JSON file using the Python script in Section 6.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
fpdfError 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
