Colocando o Restaurantix em produção com Docker, Fly.io, CI/CD 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"
};
export default
} 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!