Deploying Your Own Instance of Frankly
This guide explains how to host your own production-ready instance of Frankly, a Flutter web app backed by Firebase and Google Cloud.
Frankly uses:
- A Flutter client application (web)
- Firebase Hosting
- Firebase Authentication
- Cloud Firestore
- Realtime Database
- Firebase Functions
- Third-party integrations (some optional), including Agora, Mux, Cloudinary, and SendGrid
Prerequisites
Before you begin, make sure you have:
- A Google account with access to create Firebase and Google Cloud projects
- Firebase CLI installed (
npm install -g firebase-tools) - Node.js and npm installed (recommend via
nvm) - Flutter
3.22.2installed - Access to any third-party services you plan to enable
Recommended tools:
- Google Cloud SDK (
gcloud) - Chrome
- VS Code
1. Create Firebase Projects
Create separate Firebase projects for each environment you want to support, for example:
- staging
- production
For each project:
- Go to the Firebase Console
- Create a new project
- Add a Web App (under Project Settings → Your Apps)
- Note the Firebase web configuration values — you will use them in
client/.envlater
2. Update .firebaserc
Update .firebaserc at the repo root to point at your Firebase project IDs.
Example structure:
{
"projects": {
"default": "your-staging-project-id",
"staging": "your-staging-project-id",
"prod": "your-production-project-id"
}
}
3. Initialize Core Firebase Services
For each Firebase project/environment, set up the following in the Firebase Console.
Firestore
- Create a Firestore database (single-region configuration required)
- Deploy Firestore rules:
firebase deploy --only firestore:rules
Realtime Database
- Create a Realtime Database
- Deploy Realtime Database rules:
firebase deploy --only database
Firebase Authentication
Enable the required auth providers in the Firebase Console under Authentication → Sign-in method:
- Anonymous — Required. New visitors are signed in anonymously.
- Email/Password
For Google sign-in, you will also need to set the Google OAuth Client ID in client/web/index.html. The file contains the placeholder __GOOGLE_ID__ which must be replaced with your OAuth client ID before building.
4. Configure Firebase Hosting
Frankly is hosted via Firebase Hosting. The built Flutter web app is served from client/build/web.
If you are using multiple hosting targets (e.g. staging and production sites), configure them with:
firebase target:apply hosting staging your-staging-site-name
firebase target:apply hosting prod your-production-site-name
Also configure your custom domains in Firebase Hosting for each target as needed.
For more information, see Firebase Hosting deploy targets.
5. Configure Firebase Functions
Frankly uses Firebase Functions. Before deploying, verify that the default service account for the Firebase project is enabled:
- Firebase Console → Project Settings → Service Accounts
- Click Manage service accounts in Google Cloud Console
- Confirm the required service account is enabled
Install dependencies and build functions
From firebase/functions:
npm install
dart pub get
dart run build_runner build --output=build
Deploy functions
firebase deploy --only functions
Inspect current runtime config
firebase functions:config:get
6. Create Required Cloud Tasks Queue
Frankly uses Google Cloud Tasks for scheduled function work.
gcloud config set project YOUR-FIREBASE-PROJECT-ID
gcloud tasks queues create scheduled-functions
Repeat for each environment as needed.
7. Deploy Rules and Indexes
firebase deploy --only firestore:rules
firebase deploy --only database
firebase deploy --only firestore:indexes
8. Set Functions Runtime Configuration
Frankly uses firebase functions:config for server-side configuration. These values are read by Firebase Functions at runtime — they are not the same as the client .env variables covered in the next section.
A sample configuration file is at firebase/functions/.runtimeconfig.json.example. Copy it to firebase/functions/.runtimeconfig.json for local development.
For production, set values using the Firebase CLI:
firebase functions:config:set \
app.domain="<YOUR_VALUE_HERE>" \
app.legal_entity_name="<YOUR_VALUE_HERE>" \
app.alt_email_domain="<YOUR_VALUE_HERE>" \
app.no_reply_email="<YOUR_VALUE_HERE>" \
app.unsubscribe_encryption_key="<YOUR_VALUE_HERE>" \
app.name="<YOUR_VALUE_HERE>" \
app.full_url="<YOUR_VALUE_HERE>" \
app.privacy_policy_url="<YOUR_VALUE_HERE>" \
app.mailing_address="<YOUR_VALUE_HERE>" \
app.banner_image_url="<YOUR_VALUE_HERE>" \
app.functions_url_prefix="<YOUR_VALUE_HERE>" \
app.project_id="<YOUR_VALUE_HERE>" \
app.copyright="<YOUR_VALUE_HERE>" \
ics.prod_id="<YOUR_VALUE_HERE>" \
xmlns.url="<YOUR_VALUE_HERE>" \
xmlns.media_url="<YOUR_VALUE_HERE>" \
functions.on_firestore.min_instances="<YOUR_VALUE_HERE>" \
functions.update_live_stream_participant_count.min_instances="<YOUR_VALUE_HERE>"
What these values are for
Application settings
app.domain— App domain without protocol, e.g.www.example.comapp.legal_entity_name— Legal owner/operator of the app (may contain HTML)app.alt_email_domain— Domain used for SendGrid mail sendingapp.no_reply_email— No-reply sender email addressapp.name— Public-facing application nameapp.full_url— Full app URL including protocol, e.g.https://www.example.comapp.privacy_policy_url— Full privacy policy URLapp.mailing_address— Physical mailing address for outgoing email contentapp.banner_image_url— Home page banner image URLapp.functions_url_prefix— Base URL for deployed Firebase Functionsapp.project_id— Firebase project IDapp.copyright— Copyright statement for outgoing emailsapp.unsubscribe_encryption_key— Random string used for unsubscribe link signing
Calendar / XML namespace values
ics.prod_id— Product identifier string for ICS calendar generationxmlns.url— App URL plus/xmlnsxmlns.media_url— Media RSS namespace URL (typicallyhttp://search.yahoo.com/mrss/)
Function scaling controls
functions.on_firestore.min_instances— Minimum warm instances for the main Firestore functionfunctions.update_live_stream_participant_count.min_instances— Minimum warm instances for the participant count function
These values may differ between staging and production.
9. Configure the Flutter Client
All client configuration is done via a .env file. The Flutter build reads this file using --dart-define-from-file. No source files need to be edited for configuration except the Google Sign-In client ID placeholder in client/web/index.html (see below).
Copy client/.env.hosted.example to client/.env and fill in all required values:
cp client/.env.hosted.example client/.env
Required values in client/.env
Firebase connection — obtain from Firebase Console → Project Settings → Your Apps → Web App:
FIREBASE_API_KEY=<value>
FIREBASE_APP_ID=<value>
FIREBASE_MESSAGING_SENDER_ID=<value>
FIREBASE_PROJECT_ID=<value>
FIREBASE_AUTH_DOMAIN=<value>
FIREBASE_DATABASE_URL=<value>
FIREBASE_STORAGE_BUCKET=<value>
FIREBASE_MEASUREMENT_ID=<value>
App connections:
FUNCTIONS_URL_PREFIX=<value> # Base URL for your Google Cloud Run Functions
SHARE_LINK_URL=<value> # URL prefix for share links, e.g. https://<app-url>/share
Cloudinary (for media uploads):
CLOUDINARY_IMAGE_PRESET=<value>
CLOUDINARY_VIDEO_PRESET=<value>
CLOUDINARY_DEFAULT_PRESET=<value>
CLOUDINARY_CLOUD_NAME=<value>
Optional / branding — defaults are set in the example file and can be overridden:
APP_NAME=
APP_URL=
LOGO_URL=
SIDEBAR_FOOTER=
COPYRIGHT_STATEMENT=
TERMS_URL=
PRIVACY_POLICY_URL=
# ... (see .env.hosted.example for full list)
Optional monitoring:
SENTRY_DSN=<value>
SENTRY_ENVIRONMENT=<value>
SENTRY_RELEASE=<value>
MATOMO_URL=<value>
MATOMO_SITE_ID=<value>
Google Sign-In Client ID
The file client/web/index.html contains the placeholder __GOOGLE_ID__ in a <meta name="google-signin-client_id"> tag. Replace this with your Google OAuth client ID before building.
10. Optional Third-Party Integrations
You only need to configure the services your instance will use.
Agora
Agora powers video functionality. Set these values via firebase functions:config:set:
agora.app_id="<YOUR_VALUE_HERE>"
agora.app_certificate="<YOUR_VALUE_HERE>"
agora.rest_key="<YOUR_VALUE_HERE>"
agora.rest_secret="<YOUR_VALUE_HERE>"
agora.storage_bucket_name="<YOUR_VALUE_HERE>"
agora.storage_access_key="<YOUR_VALUE_HERE>"
agora.storage_secret_key="<YOUR_VALUE_HERE>"
agora.webhook_secret="<YOUR_VALUE_HERE>"
Setup summary:
- Create a project in the Agora console using Secure Mode: App ID + Token
app_id/app_certificate: from the project page in the Agora consolerest_key/rest_secret: from Developer Toolkit → Restful API → Add a Secretstorage_bucket_name/storage_access_key/storage_secret_key: create a Google Cloud Storage bucket for recordings and generate interoperability access keys under Storage → Settings → Interoperabilitywebhook_secret: a secret string you choose when configuring Agora Notifications to point at your deployedAgoraRecordingWebhookfunction URL
Mux
Mux supports livestream workflows. Set via firebase functions:config:set:
mux.token_id="<YOUR_VALUE_HERE>"
mux.secret="<YOUR_VALUE_HERE>"
Also configure Mux webhooks in the Mux dashboard to POST to your deployed MuxWebhooks function URL.
Cloudinary
Cloudinary is used for media uploads. Configuration is done entirely via client/.env (see step 9). No source files need to be edited.
Stripe
Stripe is currently disabled by default. If you enable it, set via firebase functions:config:set:
stripe.connected_account_webhook_key="<YOUR_VALUE_HERE>"
stripe.pub_key="<YOUR_VALUE_HERE>"
stripe.secret_key="<YOUR_VALUE_HERE>"
stripe.webhook_key="<YOUR_VALUE_HERE>"
Also create products and pricing with a plan_type metadata field (individual, club, or pro) and configure prices for each.
SendGrid
SendGrid email delivery is handled through a Firestore extension. Configure the Firebase extension firebase/firestore-send-email@0.1.9 with your SendGrid credentials. Email definitions are written to the sendgridemail Firestore collection.
11. Build and Deploy
Step 1: Select the target project
firebase use staging
# or
firebase use prod
Step 2: Deploy rules and indexes
firebase deploy --only firestore:rules
firebase deploy --only database
firebase deploy --only firestore:indexes
Step 3: Build and deploy functions
From firebase/functions:
npm install
dart run build_runner build --output=build
firebase deploy --only functions
Step 4: Build the Flutter client
From client/:
flutter pub get
flutter build web --release --source-maps --web-renderer html -t lib/main.dart --dart-define-from-file=.env
The built output goes to client/build/web, which is what Firebase Hosting serves (as configured in firebase.json).
Step 5: Deploy hosting
From the repo root:
firebase deploy --only hosting
12. Post-Deployment Verification Checklist
Core platform
- Firebase Hosting site loads successfully
- Anonymous auth works for new visitors
- Email/password auth works
- Google auth works
- Firestore reads/writes succeed
- Realtime Database connectivity works
- Firebase Functions deploy with no runtime config errors
Video / events
- Users can create or join events
- Breakout room assignment works
- Audio/video works in browser
- Recording works if Agora recording is configured
Livestreaming
- Mux webhook is reachable
- Live stream playback appears correctly
Media and messaging
- Cloudinary uploads succeed
- SendGrid emails are delivered
- Banner images and public assets load
Infrastructure
- Cloud Tasks queue exists
- Firestore indexes are built
- Rules are deployed to the correct environment
13. Recommended Environment Rollout Process
- Set up staging first
- Configure all secrets and Firebase services
- Deploy rules, indexes, functions, and hosting
- Verify authentication, event flow, and integrations
- Repeat for production
- Keep a separate config checklist for each environment
14. Common Pitfalls
Functions deploy but fail at runtime
Usually caused by missing firebase functions:config values. Check with:
firebase functions:config:get
Wrong Firebase project deployed
Confirm your active project before running deploy commands:
firebase use
Hosting works but client points at wrong backend
Double-check your client/.env values — specifically FIREBASE_PROJECT_ID, FIREBASE_AUTH_DOMAIN, FIREBASE_DATABASE_URL, and FUNCTIONS_URL_PREFIX.
Google sign-in does not work
Ensure __GOOGLE_ID__ in client/web/index.html was replaced with your actual Google OAuth client ID before building.
Video or integrations do not work
Usually caused by:
- Missing third-party credentials in
firebase functions:config - Wrong webhook URLs
- Missing storage bucket setup
- Missing Cloudinary presets in
client/.env - Missing SendGrid extension configuration
Scheduled functions do not run
Check whether the Cloud Tasks queue exists:
gcloud tasks queues list
15. Minimal Deployment Checklist
- Create Firebase projects (staging and/or production)
- Add a Web App in each Firebase project and note the config values
- Update
.firebasercwith your project IDs - Enable Anonymous, Email/Password, and Google auth providers
- Create Firestore and Realtime Database
- Deploy Firestore rules, Realtime Database rules, and Firestore indexes
- Set required
firebase functions:configvalues - Build and deploy Firebase Functions
- Create
scheduled-functionsCloud Tasks queue - Copy
client/.env.hosted.exampletoclient/.envand fill in all values - Replace
__GOOGLE_ID__inclient/web/index.htmlwith your Google OAuth client ID - Build Flutter client with
--dart-define-from-file=.env - Deploy Firebase Hosting
- Verify sign-in, events, and integrations
16. Useful Commands Reference
# Select active Firebase project
firebase use staging
firebase use prod
# Deploy rules and indexes
firebase deploy --only firestore:rules
firebase deploy --only database
firebase deploy --only firestore:indexes
# Build and deploy functions (from firebase/functions)
npm install
dart pub get
dart run build_runner build --output=build
firebase deploy --only functions
# Inspect / set runtime config
firebase functions:config:get
firebase functions:config:set app.name="Your App Name"
# Create Cloud Tasks queue
gcloud config set project YOUR-FIREBASE-PROJECT-ID
gcloud tasks queues create scheduled-functions
# Build the Flutter client (from client/)
flutter build web --release --source-maps --web-renderer html -t lib/main.dart --dart-define-from-file=.env
# Deploy hosting
firebase deploy --only hosting