Deployment Strategy Guide
Overviewβ
This guide outlines the comprehensive deployment strategy for both toto-app and toto-bo applications, ensuring reliable, scalable, and maintainable production deployments.
1. Environment Architectureβ
Environment Separation Strategyβ
# Environment Configuration
environments:
development:
toto-app: localhost:4000
toto-bo: localhost:5000
database: firebase-emulator
monitoring: local
staging:
toto-app: https://stg.app.betoto.pet
toto-bo: https://stg.bo.betoto.pet
database: toto-f9d2f-stg
monitoring: staging-dashboard
production:
toto-app: https://app.betoto.pet
toto-bo: https://bo.betoto.pet
database: toto-f9d2f
monitoring: production-dashboard
Firebase Project Structureβ
# Firebase Projects
projects:
toto-f9d2f: # Main app production
environment: production
region: us-central1
services: [apphosting, firestore, auth, storage, functions]
toto-f9d2f-stg: # Main app staging
environment: staging
region: us-central1
services: [apphosting, firestore, auth, storage, functions]
toto-bo: # Backoffice production
environment: production
region: us-central1
services: [apphosting, firestore, auth, storage, functions]
toto-bo-stg: # Backoffice staging
environment: staging
region: us-central1
services: [apphosting, firestore, auth, storage, functions]
2. CI/CD Pipeline Architectureβ
GitHub Actions Workflowβ
# .github/workflows/deploy.yml
name: Deploy Toto Ecosystem
on:
push:
branches: [main, staging]
pull_request:
branches: [main, staging]
env:
NODE_VERSION: '18'
FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
jobs:
# Test and Build Phase
test-and-build:
runs-on: ubuntu-latest
strategy:
matrix:
project: [toto-app, toto-bo]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
cache-dependency-path: ${{ matrix.project }}/package-lock.json
- name: Install dependencies
working-directory: ${{ matrix.project }}
run: npm ci
- name: Run linting
working-directory: ${{ matrix.project }}
run: npm run lint
- name: Run unit tests
working-directory: ${{ matrix.project }}
run: npm run test:coverage
- name: Run integration tests
working-directory: ${{ matrix.project }}
run: npm run test:integration
- name: Build application
working-directory: ${{ matrix.project }}
run: npm run build
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.project }}-build
path: ${{ matrix.project }}/.next
# Security Scanning
security-scan:
runs-on: ubuntu-latest
needs: test-and-build
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run security audit
run: |
cd toto-app && npm audit --audit-level=moderate
cd ../toto-bo && npm audit --audit-level=moderate
- name: Run dependency check
uses: actions/dependency-review-action@v4
- name: Run SAST scan
uses: github/super-linter@v4
env:
DEFAULT_BRANCH: main
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Deploy to Staging
deploy-staging:
runs-on: ubuntu-latest
needs: [test-and-build, security-scan]
if: github.ref == 'refs/heads/staging'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: toto-app-build
path: toto-app/.next
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: toto-bo-build
path: toto-bo/.next
- name: Deploy toto-app to staging
working-directory: toto-app
run: |
firebase use toto-f9d2f-stg
firebase deploy --only apphosting
- name: Deploy toto-bo to staging
working-directory: toto-bo
run: |
firebase use toto-bo-stg
firebase deploy --only apphosting
- name: Run smoke tests
run: |
npm run test:smoke:staging
# Deploy to Production
deploy-production:
runs-on: ubuntu-latest
needs: [test-and-build, security-scan]
if: github.ref == 'refs/heads/main'
environment: production
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: toto-app-build
path: toto-app/.next
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: toto-bo-build
path: toto-bo/.next
- name: Deploy toto-app to production
working-directory: toto-app
run: |
firebase use toto-f9d2f
firebase deploy --only apphosting
- name: Deploy toto-bo to production
working-directory: toto-bo
run: |
firebase use toto-bo
firebase deploy --only apphosting
- name: Run production smoke tests
run: |
npm run test:smoke:production
- name: Notify deployment success
uses: 8398a7/action-slack@v3
with:
status: success
text: 'Toto ecosystem deployed successfully to production'
3. Firebase App Hosting Configurationβ
toto-app Production Configurationβ
# toto-app/apphosting.production.yaml
runConfig:
minInstances: 2
maxInstances: 100
concurrency: 100
cpu: 2
memoryMiB: 1024
timeoutSeconds: 300
scripts:
buildCommand: npm run build
runCommand: npm start
env:
- variable: NODE_ENV
value: "production"
availability: [BUILD, RUNTIME]
- variable: NEXT_PUBLIC_ENVIRONMENT
value: "production"
availability: [BUILD, RUNTIME]
- variable: JWT_SECRET
secret: toto-f9d2f-jwt-secret
availability: [BUILD, RUNTIME]
- variable: NEXT_PUBLIC_FIREBASE_API_KEY
secret: toto-f9d2f-firebase-api-key
availability: [BUILD, RUNTIME]
- variable: NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN
value: toto-f9d2f.firebaseapp.com
availability: [BUILD, RUNTIME]
- variable: NEXT_PUBLIC_FIREBASE_PROJECT_ID
value: toto-f9d2f
availability: [BUILD, RUNTIME]
- variable: NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET
value: toto-f9d2f.firebasestorage.app
availability: [BUILD, RUNTIME]
- variable: NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID
secret: toto-f9d2f-firebase-sender-id
availability: [BUILD, RUNTIME]
- variable: NEXT_PUBLIC_FIREBASE_APP_ID
secret: toto-f9d2f-firebase-app-id
availability: [BUILD, RUNTIME]
- variable: FIREBASE_PROJECT_ID
value: toto-f9d2f
availability: [BUILD, RUNTIME]
- variable: STRIPE_SECRET_KEY
secret: toto-f9d2f-stripe-secret-key
availability: [BUILD, RUNTIME]
- variable: NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
secret: toto-f9d2f-stripe-publishable-key
availability: [BUILD, RUNTIME]
- variable: GOOGLE_AI_API_KEY
secret: toto-f9d2f-google-ai-key
availability: [BUILD, RUNTIME]
outputFiles:
serverApp:
include: [.next, public, package.json, next.config.js]
toto-bo Production Configurationβ
# toto-bo/apphosting.production.yaml
runConfig:
minInstances: 1
maxInstances: 50
concurrency: 80
cpu: 1
memoryMiB: 512
timeoutSeconds: 300
scripts:
buildCommand: npm run build
runCommand: npm start
env:
- variable: NODE_ENV
value: "production"
availability: [BUILD, RUNTIME]
- variable: NEXT_PUBLIC_ENVIRONMENT
value: "production"
availability: [BUILD, RUNTIME]
- variable: JWT_SECRET
secret: toto-bo-jwt-secret
availability: [BUILD, RUNTIME]
- variable: GOOGLE_CLIENT_ID
secret: toto-bo-google-client-id
availability: [BUILD, RUNTIME]
- variable: GOOGLE_CLIENT_SECRET
secret: toto-bo-google-client-secret
availability: [BUILD, RUNTIME]
- variable: NEXTAUTH_URL
value: "https://bo.betoto.pet"
availability: [BUILD, RUNTIME]
- variable: NEXTAUTH_SECRET
secret: toto-bo-nextauth-secret
availability: [BUILD, RUNTIME]
- variable: FIREBASE_PROJECT_ID
value: toto-bo
availability: [BUILD, RUNTIME]
- variable: FIREBASE_PRIVATE_KEY
secret: toto-bo-firebase-private-key
availability: [BUILD, RUNTIME]
- variable: FIREBASE_CLIENT_EMAIL
secret: toto-bo-firebase-client-email
availability: [BUILD, RUNTIME]
outputFiles:
serverApp:
include: [.next, public, package.json, next.config.js]
4. Database Migration Strategyβ
Firestore Migration Pipelineβ
// scripts/migrate-database.ts
import { initializeApp, getApps } from 'firebase/app';
import { getFirestore, doc, setDoc, getDoc } from 'firebase/firestore';
interface Migration {
version: string;
description: string;
up: () => Promise<void>;
down: () => Promise<void>;
}
const migrations: Migration[] = [
{
version: '1.0.0',
description: 'Initial schema setup',
up: async () => {
// Create initial collections and indexes
console.log('Running migration 1.0.0');
},
down: async () => {
// Rollback initial schema
console.log('Rolling back migration 1.0.0');
}
},
{
version: '1.1.0',
description: 'Add user roles and permissions',
up: async () => {
// Add role field to users collection
console.log('Running migration 1.1.0');
},
down: async () => {
// Remove role field from users collection
console.log('Rolling back migration 1.1.0');
}
}
];
export class DatabaseMigrator {
private db: any;
private currentVersion: string = '0.0.0';
constructor() {
const app = getApps()[0] || initializeApp({
// Firebase config
});
this.db = getFirestore(app);
}
async getCurrentVersion(): Promise<string> {
const versionDoc = await getDoc(doc(this.db, '_migrations', 'version'));
return versionDoc.exists() ? versionDoc.data()?.version || '0.0.0' : '0.0.0';
}
async setVersion(version: string): Promise<void> {
await setDoc(doc(this.db, '_migrations', 'version'), { version });
}
async migrate(): Promise<void> {
this.currentVersion = await this.getCurrentVersion();
console.log(`Current database version: ${this.currentVersion}`);
const pendingMigrations = migrations.filter(
migration => migration.version > this.currentVersion
);
if (pendingMigrations.length === 0) {
console.log('No pending migrations');
return;
}
console.log(`Found ${pendingMigrations.length} pending migrations`);
for (const migration of pendingMigrations) {
try {
console.log(`Running migration ${migration.version}: ${migration.description}`);
await migration.up();
await this.setVersion(migration.version);
console.log(`Migration ${migration.version} completed successfully`);
} catch (error) {
console.error(`Migration ${migration.version} failed:`, error);
throw error;
}
}
console.log('All migrations completed successfully');
}
async rollback(targetVersion: string): Promise<void> {
this.currentVersion = await this.getCurrentVersion();
console.log(`Rolling back from ${this.currentVersion} to ${targetVersion}`);
const rollbackMigrations = migrations
.filter(migration => migration.version > targetVersion && migration.version <= this.currentVersion)
.reverse();
for (const migration of rollbackMigrations) {
try {
console.log(`Rolling back migration ${migration.version}`);
await migration.down();
await this.setVersion(migration.version);
console.log(`Rollback ${migration.version} completed successfully`);
} catch (error) {
console.error(`Rollback ${migration.version} failed:`, error);
throw error;
}
}
console.log('Rollback completed successfully');
}
}
// CLI usage
if (require.main === module) {
const migrator = new DatabaseMigrator();
const command = process.argv[2];
const targetVersion = process.argv[3];
if (command === 'migrate') {
migrator.migrate().catch(console.error);
} else if (command === 'rollback' && targetVersion) {
migrator.rollback(targetVersion).catch(console.error);
} else {
console.log('Usage: npm run migrate [migrate|rollback] [targetVersion]');
}
}
5. Blue-Green Deployment Strategyβ
Zero-Downtime Deploymentβ
// scripts/blue-green-deployment.ts
import { execSync } from 'child_process';
export class BlueGreenDeployer {
private project: string;
private environment: string;
constructor(project: string, environment: string) {
this.project = project;
this.environment = environment;
}
async deploy(): Promise<void> {
console.log(`Starting blue-green deployment for ${this.project} in ${this.environment}`);
try {
// Step 1: Deploy to green environment
await this.deployToGreen();
// Step 2: Run health checks
await this.runHealthChecks();
// Step 3: Switch traffic to green
await this.switchTraffic();
// Step 4: Clean up blue environment
await this.cleanupBlue();
console.log('Blue-green deployment completed successfully');
} catch (error) {
console.error('Blue-green deployment failed:', error);
await this.rollback();
throw error;
}
}
private async deployToGreen(): Promise<void> {
console.log('Deploying to green environment...');
// Deploy to staging first for validation
execSync(`firebase use ${this.project}-stg`, { stdio: 'inherit' });
execSync(`firebase deploy --only apphosting`, { stdio: 'inherit' });
// Run integration tests
execSync(`npm run test:integration:staging`, { stdio: 'inherit' });
// Deploy to production green
execSync(`firebase use ${this.project}`, { stdio: 'inherit' });
execSync(`firebase deploy --only apphosting`, { stdio: 'inherit' });
}
private async runHealthChecks(): Promise<void> {
console.log('Running health checks...');
const healthCheckUrl = this.getHealthCheckUrl();
const maxRetries = 10;
const retryDelay = 5000;
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(healthCheckUrl);
if (response.ok) {
console.log('Health checks passed');
return;
}
} catch (error) {
console.log(`Health check attempt ${i + 1} failed:`, error);
}
if (i < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, retryDelay));
}
}
throw new Error('Health checks failed after maximum retries');
}
private async switchTraffic(): Promise<void> {
console.log('Switching traffic to green environment...');
// Update DNS or load balancer configuration
// This would typically involve updating CloudFlare or similar service
console.log('Traffic switched to green environment');
}
private async cleanupBlue(): Promise<void> {
console.log('Cleaning up blue environment...');
// Clean up old deployment artifacts
console.log('Blue environment cleaned up');
}
private async rollback(): Promise<void> {
console.log('Rolling back deployment...');
// Switch traffic back to blue
await this.switchTraffic();
// Clean up failed green deployment
console.log('Rollback completed');
}
private getHealthCheckUrl(): string {
const baseUrl = this.environment === 'production'
? 'https://app.betoto.pet'
: 'https://stg.app.betoto.pet';
return `${baseUrl}/api/health`;
}
}
6. Monitoring and Alertingβ
Deployment Monitoringβ
// src/lib/monitoring/deploymentMonitor.ts
export class DeploymentMonitor {
static async trackDeployment(deployment: {
project: string;
environment: string;
version: string;
status: 'started' | 'success' | 'failed';
duration?: number;
error?: string;
}): Promise<void> {
const deploymentLog = {
...deployment,
timestamp: new Date(),
id: crypto.randomUUID()
};
// Log to monitoring system
console.log('Deployment tracked:', deploymentLog);
// Send alerts for failed deployments
if (deployment.status === 'failed') {
await this.sendDeploymentAlert(deploymentLog);
}
}
private static async sendDeploymentAlert(deployment: any): Promise<void> {
// Send alert to monitoring system
console.error('Deployment failed:', deployment);
}
static async getDeploymentHistory(
project: string,
environment: string,
limit: number = 50
): Promise<any[]> {
// Retrieve deployment history from monitoring system
return [];
}
}
7. Rollback Strategyβ
Rollback Proceduresβ
If deployment issues are detected, follow these rollback procedures:
- Immediate Rollback: Switch traffic back to previous version
- Health Check Verification: Verify previous version is healthy
- Issue Investigation: Investigate root cause of deployment failure
- Fix and Redeploy: Fix issues and redeploy following standard procedures
Automated Rollback Proceduresβ
// scripts/rollback.ts
import { execSync } from 'child_process';
export class RollbackManager {
private project: string;
private environment: string;
constructor(project: string, environment: string) {
this.project = project;
this.environment = environment;
}
async rollback(targetVersion?: string): Promise<void> {
console.log(`Starting rollback for ${this.project} in ${this.environment}`);
try {
// Get current version
const currentVersion = await this.getCurrentVersion();
console.log(`Current version: ${currentVersion}`);
// Determine target version
const rollbackVersion = targetVersion || await this.getPreviousVersion();
console.log(`Rolling back to version: ${rollbackVersion}`);
// Rollback database if needed
await this.rollbackDatabase(rollbackVersion);
// Rollback application
await this.rollbackApplication(rollbackVersion);
// Verify rollback
await this.verifyRollback();
console.log('Rollback completed successfully');
} catch (error) {
console.error('Rollback failed:', error);
throw error;
}
}
private async getCurrentVersion(): Promise<string> {
// Get current deployed version
return '1.0.0'; // Placeholder
}
private async getPreviousVersion(): Promise<string> {
// Get previous stable version
return '0.9.0'; // Placeholder
}
private async rollbackDatabase(version: string): Promise<void> {
console.log(`Rolling back database to version ${version}`);
// Implement database rollback logic
}
private async rollbackApplication(version: string): Promise<void> {
console.log(`Rolling back application to version ${version}`);
// Deploy previous version
execSync(`firebase use ${this.project}`, { stdio: 'inherit' });
execSync(`firebase deploy --only apphosting`, { stdio: 'inherit' });
}
private async verifyRollback(): Promise<void> {
console.log('Verifying rollback...');
// Run health checks
const healthCheckUrl = this.getHealthCheckUrl();
const response = await fetch(healthCheckUrl);
if (!response.ok) {
throw new Error('Rollback verification failed');
}
console.log('Rollback verification successful');
}
private getHealthCheckUrl(): string {
const baseUrl = this.environment === 'production'
? 'https://app.betoto.pet'
: 'https://stg.app.betoto.pet';
return `${baseUrl}/api/health`;
}
}
8. Deployment Checklistβ
Pre-Deployment Checklistβ
-
Code Quality
- All tests passing
- Code review completed
- Security scan passed
- Performance benchmarks met
-
Configuration
- Environment variables configured
- Secrets properly stored
- Database migrations ready
- Feature flags configured
-
Infrastructure
- Firebase projects configured
- App Hosting settings updated
- Monitoring configured
- Backup procedures ready
Deployment Checklistβ
-
Staging Deployment
- Deploy to staging environment
- Run integration tests
- Verify functionality
- Performance testing
-
Production Deployment
- Blue-green deployment ready
- Health checks configured
- Rollback plan prepared
- Monitoring alerts active
Post-Deployment Checklistβ
-
Verification
- Health checks passing
- Key functionality verified
- Performance metrics normal
- Error rates acceptable
-
Monitoring
- Alerts configured
- Dashboards updated
- Logs being collected
- Metrics being tracked
9. Deployment Commandsβ
Development Commandsβ
# Start development servers
cd toto-app && npm run dev
cd toto-bo && npm run dev
# Run tests
npm run test
npm run test:coverage
npm run test:e2e
# Build applications
npm run build
npm run analyze
Staging Commandsβ
# Deploy to staging
firebase use toto-f9d2f-stg
firebase deploy --only apphosting
# Run staging tests
npm run test:staging
npm run test:smoke:staging
Production Commandsβ
# Deploy to production
firebase use toto-f9d2f
firebase deploy --only apphosting
# Run production tests
npm run test:production
npm run test:smoke:production
# Rollback if needed
npm run rollback
This deployment strategy ensures reliable, scalable, and maintainable deployments for the Toto ecosystem with zero-downtime capabilities and comprehensive monitoring.