Purpose: Production-ready database management for Penny
Last Updated: November 17, 2025
- β Zero runtime errors from missing indexes or permissions
- β Automated deployments via CI/CD
- β Version-controlled schema (Infrastructure as Code)
- β Reproducible environments (dev, staging, prod)
- β Safe migrations with rollback capability
- β All query indexes pre-defined
- β Composite indexes for complex queries
- β Automatically deployed on push to main
- β Comprehensive access control
- β Version controlled
- β Automatically validated and deployed
- β Auto-deploy on changes to database files
- β Validation before deployment
- β Manual trigger option
- β Complete collection schemas
- β Index requirements documented
- β Security model explained
npm install -g firebase-toolsfirebase loginfirebase init
# Select:
# - Firestore (rules and indexes)
# - Storagefirebase use penny-f4acd# Edit DATABASE_SCHEMA.md
# Document new collection or field# Edit firestore.rules
# Add/modify access control# Edit firestore.indexes.json
# Add composite indexes for new queries# Start Firebase emulators
firebase emulators:start
# Run your app against emulators
# FIRESTORE_EMULATOR_HOST=localhost:8080 npm run devgit add firestore.rules firestore.indexes.json DATABASE_SCHEMA.md
git commit -m "feat: Add new collection for [feature]"
git push origin main
# CI/CD will automatically deploy! πStart emulators:
firebase emulators:start --only firestoreRun against emulators:
# In another terminal
FIRESTORE_EMULATOR_HOST=localhost:8080 npm run devBenefits:
- No cloud costs
- Fast iteration
- Isolated testing
- Can seed test data
Create test file: tests/firestore.rules.test.js
const { initializeTestEnvironment, assertSucceeds, assertFails } = require('@firebase/rules-unit-testing');
describe('Firestore Rules', () => {
let testEnv;
beforeAll(async () => {
testEnv = await initializeTestEnvironment({
projectId: 'penny-test',
firestore: {
rules: fs.readFileSync('firestore.rules', 'utf8'),
},
});
});
test('Users can only read their own notifications', async () => {
const alice = testEnv.authenticatedContext('alice');
const bob = testEnv.authenticatedContext('bob');
// Alice creates notification for herself
await assertSucceeds(
alice.firestore()
.collection('notifications')
.doc('notif1')
.set({ userId: 'alice', title: 'Test' })
);
// Alice can read her own notification
await assertSucceeds(
alice.firestore()
.collection('notifications')
.doc('notif1')
.get()
);
// Bob CANNOT read Alice's notification
await assertFails(
bob.firestore()
.collection('notifications')
.doc('notif1')
.get()
);
});
});Run tests:
npm test -- firestore.rules.test.jsCheck if query has index:
// In your code, queries will throw error if index missing
// Example: This query REQUIRES an index
const q = query(
collection(db, 'notifications'),
where('userId', '==', userId),
where('read', '==', false),
orderBy('createdAt', 'desc')
);
// If index missing, you'll see:
// "The query requires an index. You can create it here: [URL]"Solution: Add to firestore.indexes.json BEFORE deploying!
# Use Firebase emulators (free, local)
firebase emulators:start
# Connect your app
export FIRESTORE_EMULATOR_HOST="localhost:8080"
npm run dev# Create separate Firebase project for staging
firebase projects:create penny-staging
# Use staging project
firebase use penny-staging
# Deploy
firebase deploy --only firestore# Use production project
firebase use penny-f4acd
# Deploy via CI/CD (automatic)
# Or manually:
firebase deploy --only firestoreSet these in your GitHub repository:
-
FIREBASE_TOKEN# Generate token locally firebase login:ci # Copy the token # Add to GitHub: Settings β Secrets β Actions β New secret # Name: FIREBASE_TOKEN # Value: [paste token]
-
FIREBASE_PROJECT_IDName: FIREBASE_PROJECT_ID Value: penny-f4acd
The workflow runs automatically when:
- Push to
mainbranch - Changes to:
firestore.rules,firestore.indexes.json,storage.rules,firebase.json - Manual trigger via GitHub Actions UI
If CI/CD fails or you need manual control:
# Deploy everything
firebase deploy
# Deploy only rules
firebase deploy --only firestore:rules
# Deploy only indexes
firebase deploy --only firestore:indexes
# Deploy only storage rules
firebase deploy --only storageAccess: https://console.firebase.google.com/project/penny-f4acd
Key Metrics to Watch:
-
Firestore Usage
- Firestore β Usage
- Monitor read/write operations
- Check storage size
- Watch for spikes
-
Index Status
- Firestore β Indexes
- Check for "Building" status
- Verify all indexes are "Enabled"
- Delete unused indexes
-
Rules Evaluation
- Firestore β Rules
- Check for rule violations
- Review access patterns
Firebase Alerts:
# Go to: Project Settings β Integrations
# Enable Slack/Email notifications for:
- Budget alerts
- Security rule violations
- Quota approaching limits// In your code, log database operations
console.log('[DB] Creating notification:', {
userId,
type,
timestamp: new Date().toISOString()
});
// On errors, log details
console.error('[DB] Failed to create notification:', {
error: error.message,
userId,
type
});Problem: You wrote a query without defining an index
Solution:
- Firebase will provide a URL to auto-create index in console
- Click the URL, create index
- Export the index:
firebase firestore:indexes > firestore.indexes.json - Commit the updated file
- Next time, index will exist from CI/CD!
Problem: Security rules blocking access
Solution:
- Check
firestore.rules - Verify user is authenticated
- Ensure rule allows the operation
- Test in emulator
- Deploy updated rules
Problem: Large collections take time to index
Solution:
- Small collections: Indexes build in seconds
- Large collections (>10K docs): Can take 5-30 minutes
- Very large (>1M docs): Can take hours
Check status:
firebase firestore:indexesMonitor in console:
- Firestore β Indexes β Check "Building" status
Problem: GitHub Actions workflow failing
Debugging steps:
- Check GitHub Actions logs
- Verify
FIREBASE_TOKENis valid - Regenerate token if needed:
firebase login:ci
- Update GitHub secret with new token
Bad:
// β Loads ALL documents, then filters in memory
const snapshot = await getDocs(collection(db, 'expenses'));
const userExpenses = snapshot.docs.filter(doc => doc.data().userId === userId);Good:
// β
Firestore filters server-side (requires index)
const q = query(
collection(db, 'expenses'),
where('userId', '==', userId),
orderBy('date', 'desc'),
limit(20)
);
const snapshot = await getDocs(q);Unused indexes cost nothing in performance but add to maintenance
Audit indexes:
# Check which indexes are actually used
# Firebase Console β Firestore β Indexes
# Look for "Last Used" columnRemove unused indexes:
// Edit firestore.indexes.json
// Comment out or delete unused indexesUse cursor-based pagination, not offset:
// β
Good: Cursor-based
const q = query(
collection(db, 'notifications'),
where('userId', '==', userId),
orderBy('createdAt', 'desc'),
startAfter(lastVisible),
limit(20)
);
// β Bad: Offset-based (slow for large collections)
// Firestore doesn't support OFFSET, and emulating it is expensiveScenario: You need to add a new field to all existing documents
Create migration script: scripts/migrations/2025-11-17-add-notification-priority.js
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
async function migrate() {
const batch = db.batch();
let count = 0;
// Get all notifications without priority field
const snapshot = await db.collection('notifications')
.where('priority', '==', null)
.get();
console.log(`Found ${snapshot.size} documents to migrate`);
snapshot.docs.forEach(doc => {
batch.update(doc.ref, {
priority: 'medium', // Default value
migratedAt: admin.firestore.FieldValue.serverTimestamp()
});
count++;
// Firestore batch limit is 500
if (count === 500) {
await batch.commit();
count = 0;
batch = db.batch();
}
});
// Commit remaining
if (count > 0) {
await batch.commit();
}
console.log('Migration complete!');
}
migrate().catch(console.error);Run migration:
node scripts/migrations/2025-11-17-add-notification-priority.jsSafety checklist:
- Test on staging first
- Backup production data
- Run during low-traffic period
- Monitor for errors
- Verify data integrity after
- Have rollback plan
- β
Version control all database config (
rules,indexes, schema docs) - β Test locally with emulators before deploying
- β Use CI/CD for automatic deployments
- β
Document schema changes in
DATABASE_SCHEMA.md - β Define indexes BEFORE writing queries
- β Use composite indexes for complex queries
- β Monitor usage and costs
- β Set up staging environment
- β Create migration scripts for data changes
- β Review security rules regularly
- β Create indexes manually in console (won't be version controlled)
- β Deploy to production without testing
- β Write queries without checking for required indexes
- β Store sensitive data without encryption
- β Use admin SDK in client-side code
- β Bypass security rules (except via admin SDK server-side)
- β Delete production data without backups
- β Ignore index warnings in development
# Authentication
firebase login
firebase logout
firebase login:ci # Get CI/CD token
# Project management
firebase projects:list
firebase use penny-f4acd
firebase use --add # Add new project alias
# Deployment
firebase deploy
firebase deploy --only firestore:rules
firebase deploy --only firestore:indexes
firebase deploy --only storage
firebase deploy --except functions
# Emulators
firebase emulators:start
firebase emulators:start --only firestore
firebase emulators:export ./backups # Export emulator data
firebase emulators:start --import=./backups # Import data
# Firestore management
firebase firestore:indexes
firebase firestore:indexes:list
firebase firestore:delete --all-collections # β οΈ DANGER!
# Debugging
firebase --debug deploy # Verbose output
firebase projects:list --debug- Firebase Docs: https://firebase.google.com/docs/firestore
- Security Rules: https://firebase.google.com/docs/firestore/security/get-started
- Indexes: https://firebase.google.com/docs/firestore/query-data/indexing
- Best Practices: https://firebase.google.com/docs/firestore/best-practices
- Pricing: https://firebase.google.com/pricing
-
firestore.indexes.jsoncreated with all required indexes -
firebase.jsonupdated to reference indexes - Security rules include notification collections
- CI/CD workflow created (
.github/workflows/firebase-deploy.yml) - Database schema documented (
DATABASE_SCHEMA.md) - Best practices guide created (this document)
- GitHub secrets configured (
FIREBASE_TOKEN,FIREBASE_PROJECT_ID) - Initial deployment completed
- Staging environment set up (optional but recommended)
- Team trained on workflow
Next Steps:
- Add GitHub secrets for CI/CD
- Deploy indexes:
firebase deploy --only firestore:indexes - Verify indexes building in Firebase Console
- Test queries to ensure no index errors
- Set up monitoring and alerts
Questions? Refer to this guide or Firebase documentation!
Maintained By: DevOps Team
Last Updated: November 17, 2025
Status: β
Production Ready