Colocando o Restaurantix em produção com Docker, Fly.io e monitoramento
O Restaurantix está configurado para deploy automático no Fly.io usando Docker e GitHub Actions.
# syntax = docker/dockerfile:1 # Adjust BUN_VERSION as desired ARG BUN_VERSION=1.2.9 FROM oven/bun:${BUN_VERSION}-slim AS base LABEL fly_launch_runtime="Bun" # Bun app lives here WORKDIR /app # Set production environment ENV NODE_ENV="production" # Throw-away build stage to reduce size of final image FROM base AS build # Install packages needed to build node modules RUN apt-get update -qq && \ apt-get install --no-install-recommends -y build-essential pkg-config python-is-python3 # Install node modules COPY package.json bun.lock ./ RUN bun install # Copy application code COPY . . # Build application with correct target RUN bun run build # Clean up and install only production dependencies RUN rm -rf node_modules && \ bun install --ci # Final stage for app image FROM base # Copy built application COPY --from=build /app /app # Start the server by default, this can be overwritten at runtime EXPOSE 3000 CMD [ "bun", "run", "start" ]
// MULTI-STAGE BUILD para otimização // 1. BASE STAGE FROM oven/bun:1.2.9-slim AS base // - Imagem oficial do Bun otimizada // - Versão slim para menor tamanho // - Label para Fly.io reconhecer // 2. BUILD STAGE FROM base AS build // - Instala dependências de build (gcc, python) // - Copia package.json e bun.lock primeiro (cache layer) // - Instala dependências // - Copia código fonte // - Executa build // - Limpa node_modules e reinstala só produção // 3. FINAL STAGE FROM base // - Copia apenas o resultado do build // - Imagem final mínima // - Expõe porta 3000 // - Comando padrão: bun run start // VANTAGENS: // ✅ Imagem final pequena (~50MB vs ~200MB) // ✅ Cache eficiente das dependências // ✅ Sem ferramentas de build na produção // ✅ Startup rápido do Bun
# fly.toml app = "restaurantix" primary_region = "gru" # São Paulo [build] [env] NODE_ENV = "production" [http_service] internal_port = 3000 force_https = true auto_stop_machines = true auto_start_machines = true min_machines_running = 1 processes = ["app"] [[vm]] memory = "1gb" cpu_kind = "shared" cpus = 1 [checks] [checks.health] grace_period = "10s" interval = "30s" method = "GET" path = "/health" port = 3000 timeout = "5s" type = "http"
# 1. Instalar Fly CLI curl -L https://fly.io/install.sh | sh # 2. Login no Fly.io fly auth login # 3. Criar aplicação fly launch # Escolher região: São Paulo (gru) # Configurar PostgreSQL: Sim # 4. Configurar secrets (variáveis de ambiente) fly secrets set DATABASE_URL="postgresql://..." fly secrets set JWT_SECRET="your-super-secret-key" fly secrets set CSRF_SECRET_KEY="your-csrf-secret" fly secrets set RESEND_API_KEY="re_123456789" fly secrets set API_BASE_URL="https://restaurantix.fly.dev" fly secrets set AUTH_REDIRECT_URL="https://app.restaurantix.com" # 5. Deploy fly deploy # 6. Verificar status fly status fly logs # 7. Abrir aplicação fly open
# Criar banco PostgreSQL fly postgres create --name restaurantix-db --region gru # Conectar aplicação ao banco fly postgres attach --app restaurantix restaurantix-db # Executar migrations fly ssh console bun run migrate # Ou via proxy local fly proxy 5432 -a restaurantix-db # Em outro terminal: DATABASE_URL="postgresql://postgres:password@localhost:5432/restaurantix" bun run migrate
// src/application/infra/db/migrate.ts import { migrate } from "drizzle-orm/postgres-js/migrator"; import { db, connection } from "./connection"; async function main() { console.log("🔄 Running migrations..."); await migrate(db, { migrationsFolder: "./drizzle", }); console.log("✅ Migrations completed!"); await connection.end(); } main().catch((error) => { console.error("❌ Migration failed:", error); process.exit(1); }); // package.json { "scripts": { "migrate": "bun run src/application/infra/db/migrate.ts", "migrate:prod": "NODE_ENV=production bun run migrate" } }
name: Deploy to Fly.io on: push: branches: [main] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Bun uses: oven-sh/setup-bun@v1 with: bun-version: latest - name: Install dependencies run: bun install - name: Run tests run: bun test - name: Run linter run: bun run lint deploy: needs: test runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v4 - name: Setup Fly CLI uses: superfly/flyctl-actions/setup-flyctl@master - name: Deploy to Fly.io run: flyctl deploy --remote-only env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
# GitHub Secrets necessários: # 1. FLY_API_TOKEN # - Obter em: https://fly.io/user/personal_access_tokens # - Adicionar em: GitHub > Settings > Secrets and variables > Actions # 2. Configurar secrets no Fly.io via CLI ou dashboard fly secrets set DATABASE_URL="postgresql://..." fly secrets set JWT_SECRET="your-jwt-secret" fly secrets set CSRF_SECRET_KEY="your-csrf-secret" fly secrets set RESEND_API_KEY="re_123456789" # 3. Verificar secrets fly secrets list # FLUXO DE DEPLOY: # 1. Push para main # 2. GitHub Actions executa testes # 3. Se testes passam, faz deploy # 4. Fly.io builda Docker image # 5. Deploy com zero-downtime # 6. Health checks verificam se app está ok
// src/http/routes/health.ts import { Elysia } from "elysia"; import { db } from "@/application/infra/db/connection"; export const health = new Elysia() .get("/health", async () => { try { // Verificar conexão com banco await db.execute(sql`SELECT 1`); return { status: "ok", timestamp: new Date().toISOString(), uptime: process.uptime(), memory: process.memoryUsage(), version: process.env.npm_package_version || "unknown", environment: process.env.NODE_ENV, database: "connected" }; } catch (error) { return { status: "error", timestamp: new Date().toISOString(), error: error.message, database: "disconnected" }; } }); // Adicionar no server.ts app.use(health);
// src/utils/logger.ts export class Logger { static info(message: string, meta?: any) { console.log(JSON.stringify({ level: "info", message, timestamp: new Date().toISOString(), ...meta })); } static error(message: string, error?: Error, meta?: any) { console.error(JSON.stringify({ level: "error", message, error: error?.message, stack: error?.stack, timestamp: new Date().toISOString(), ...meta })); } static request(req: Request, responseTime: number) { console.log(JSON.stringify({ level: "info", type: "request", method: req.method, url: req.url, responseTime: `${responseTime}ms`, timestamp: new Date().toISOString() })); } } // Uso nas rotas Logger.info("User authenticated", { userId: user.id }); Logger.error("Database connection failed", error);
# Verificar status da aplicação fly status # Ver logs em tempo real fly logs # Métricas de performance fly metrics # Conectar via SSH fly ssh console # Escalar aplicação fly scale count 2 # 2 instâncias fly scale memory 2048 # 2GB RAM # Verificar health checks curl https://restaurantix.fly.dev/health # Monitorar banco de dados fly postgres connect -a restaurantix-db
Parabéns! Você completou o curso de Bun.js. Agora é hora de colocar tudo em produção:
Você dominou Bun.js e construiu um SaaS completo em produção!