Skip to main content

bunjs-production

Use when deploying Bun.js to production, containerizing with Docker, setting up AWS ECS/Fargate, implementing Redis caching, hardening security, or configuring CI/CD pipelines. See bunjs for basics, bunjs-architecture for patterns.

소스 정보

저장소
MadAppGang/claude-code
최근 소스 활동
2026년 1월 20일 08:16
감지된 SKILL.md 언어
영어
스타
284
포크
25

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
bunjs-production
version
1.0.0
description
Use when deploying Bun.js to production, containerizing with Docker, setting up AWS ECS/Fargate, implementing Redis caching, hardening security, or configuring CI/CD pipelines. See bunjs for basics, bunjs-architecture for patterns.
keywords
["production deployment","Docker","AWS ECS","Redis","caching","security","CI/CD","logging","monitoring","rate limiting"]
plugin
dev
updated
2026-01-20T00:00:00.000Z
# Bun.js Production Deployment Patterns ## Overview This skill covers production deployment patterns for Bun.js TypeScript backend applications, including Docker containerization, AWS ECS deployment, Redis caching, security hardening, structured logging, CI/CD pipelines, and production readiness checklists. **When to use this skill:** - Containerizing applications with Docker - Deploying to AWS ECS/Fargate - Implementing Redis caching strategies - Hardening security (headers, CORS, rate limiting) - Setting up CI/CD pipelines - Preparing for production deployment **See also:** - **dev:bunjs** - Core Bun patterns, HTTP servers, database access - **dev:bunjs-architecture** - Layered architecture, camelCase conventions - **dev:bunjs-apidog** - OpenAPI specifications and Apidog integration ## Docker Multi-Stage Build ### Production Dockerfile ```dockerfile # Stage 1: Base FROM oven/bun:1-alpine AS base WORKDIR /app # Stage 2: Dependencies FROM base AS deps COPY package.json bun.lockb ./ COPY prisma ./prisma/ RUN bun install --frozen-lockfile --production # Stage 3: Build FROM base AS build COPY --from=deps /app/node_modules ./node_modules COPY . . RUN bunx prisma generate RUN bun run build # Optional: if you have a build step # Stage 4: Runner FROM base AS runner WORKDIR /app ENV NODE_ENV=production # Create non-root user RUN addgroup -g 1001 bungroup && \ adduser -D -u 1001 -G bungroup bunuser # Copy dependencies and source COPY --from=deps /app/node_modules ./node_modules COPY --from=build /app/src ./src COPY --from=build /app/prisma ./prisma COPY --from=build /app/node_modules/.prisma ./node_modules/.prisma COPY package.json bun.lockb ./ # Set ownership RUN chown -R bunuser:bungroup /app USER bunuser EXPOSE 3000 # Health check HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \ CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1 CMD ["bun", "src/server.ts"] ``` ### docker-compose.yml (Local Development) ```yaml version: '3.8' services: app: build: context: . dockerfile: Dockerfile ports: - "3000:3000" environment: - NODE_ENV=development - DATABASE_URL=postgresql://user:password@postgres:5432/mydb - REDIS_URL=redis://redis:6379 depends_on: - postgres - redis volumes: - ./src:/app/src command: bun --hot src/server.ts postgres: image: postgres:17-alpine environment: POSTGRES_USER: user POSTGRES_PASSWORD: password POSTGRES_DB: mydb ports: - "5432:5432" volumes: - postgres-data:/var/lib/postgresql/data redis: image: redis:7-alpine ports: - "6379:6379" volumes: - redis-data:/data volumes: postgres-data: redis-data: ``` ### Build and Run Commands ```bash # Build image docker build -t myapp:latest . # Run locally docker-compose up -d # Run migrations docker-compose exec app bunx prisma migrate deploy # View logs docker-compose logs -f app # Stop docker-compose down ``` ## Graceful Shutdown ### Server with Shutdown Handling ```typescript // src/server.ts import { serve } from '@hono/node-server'; import { app } from './app'; import { prisma } from '@/database/client'; import { logger } from '@core/logger'; const PORT = Number(process.env.PORT) || 3000; // Start server const server = serve({ fetch: app.fetch, port: PORT }); logger.info(`🚀 Server running on port ${PORT}`); // Graceful shutdown handler async function shutdown(signal: string) { logger.info(`Received ${signal}, initiating graceful shutdown...`); try { // Close HTTP server (stop accepting new requests) server.close(); logger.info('HTTP server closed'); // Close database connections await prisma.$disconnect(); logger.info('Database connections closed'); // Close Redis connections (if used) // await redis.quit(); logger.info('Graceful shutdown complete'); process.exit(0); } catch (error) { logger.error({ error }, 'Error during shutdown'); process.exit(1); } } // Handle termination signals process.on('SIGTERM', () => shutdown('SIGTERM')); process.on('SIGINT', () => shutdown('SIGINT')); // Handle unhandled errors process.on('unhandledRejection', (reason, promise) => { logger.error({ reason, promise }, 'Unhandled promise rejection'); }); process.on('uncaughtException', (error) => { logger.error({ error }, 'Uncaught exception'); shutdown('UNCAUGHT_EXCEPTION'); }); ``` ## AWS ECS Deployment ### Task Definition (JSON) ```json { "family": "myapp", "networkMode": "awsvpc", "requiresCompatibilities": ["FARGATE"], "cpu": "256", "memory": "512", "containerDefinitions": [ { "name": "myapp", "image": "123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:latest", "essential": true, "portMappings": [ { "containerPort": 3000, "protocol": "tcp" } ], "environment": [ { "name": "NODE_ENV", "value": "production" }, { "name": "PORT", "value": "3000" } ], "secrets": [ { "name": "DATABASE_URL", "valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:myapp/DATABASE_URL" }, { "name": "JWT_SECRET", "valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:myapp/JWT_SECRET" } ], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/ecs/myapp", "awslogs-region": "us-east-1", "awslogs-stream-prefix": "ecs" } }, "healthCheck": { "command": ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1"], "interval": 30, "timeout": 5, "retries": 3, "startPeriod": 30 } } ] } ``` ### Service Definition (JSON) ```json { "serviceName": "myapp", "cluster": "production-cluster", "taskDefinition": "myapp:1", "desiredCount": 2, "launchType": "FARGATE", "networkConfiguration": { "awsvpcConfiguration": { "subnets": [ "subnet-12345678", "subnet-87654321" ], "securityGroups": [ "sg-12345678" ], "assignPublicIp": "DISABLED" } }, "loadBalancers": [ { "targetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/myapp/1234567890123456", "containerName": "myapp", "containerPort": 3000 } ], "healthCheckGracePeriodSeconds": 60 } ``` ### Deployment Script ```bash #!/bin/bash # deploy.sh set -e # Variables AWS_REGION="us-east-1" ECR_REGISTRY="123456789012.dkr.ecr.${AWS_REGION}.amazonaws.com" IMAGE_NAME="myapp" IMAGE_TAG="${GITHUB_SHA:0:7}" CLUSTER_NAME="production-cluster" SERVICE_NAME="myapp" # 1. Login to ECR aws ecr get-login-password --region $AWS_REGION | \ docker login --username AWS --password-stdin $ECR_REGISTRY # 2. Build image docker build -t $IMAGE_NAME:$IMAGE_TAG . # 3. Tag image docker tag $IMAGE_NAME:$IMAGE_TAG $ECR_REGISTRY/$IMAGE_NAME:$IMAGE_TAG docker tag $IMAGE_NAME:$IMAGE_TAG $ECR_REGISTRY/$IMAGE_NAME:latest # 4. Push to ECR docker push $ECR_REGISTRY/$IMAGE_NAME:$IMAGE_TAG docker push $ECR_REGISTRY/$IMAGE_NAME:latest # 5. Update ECS service (force new deployment) aws ecs update-service \ --cluster $CLUSTER_NAME \ --service $SERVICE_NAME \ --force-new-deployment \ --region $AWS_REGION echo "Deployment initiated. Check ECS console for status." ``` ## Caching with Redis ### Redis Client Setup ```typescript // src/utils/redis.ts import Redis from 'ioredis'; import { logger } from '@core/logger'; const redisUrl = process.env.REDIS_URL || 'redis://localhost:6379'; export const redis = new Redis(redisUrl, { maxRetriesPerRequest: 3, retryStrategy(times) { const delay = Math.min(times * 50, 2000); return delay; }, reconnectOnError(err) { logger.error({ error: err }, 'Redis connection error'); return true; } }); redis.on('connect', () => { logger.info('Redis connected'); }); redis.on('error', (err) => { logger.error({ error: err }, 'Redis error'); }); // Graceful shutdown export async function closeRedis() { await redis.quit(); logger.info('Redis connection closed'); } ``` ### Cache Utilities ```typescript // src/utils/cache.ts import { redis } from './redis'; export async function cacheGet<T>(key: string): Promise<T | null> { const value = await redis.get(key); return value ? JSON.parse(value) : null; } export async function cacheSet( key: string, value: any, ttlSeconds: number ): Promise<void> { await redis.set(key, JSON.stringify(value), 'EX', ttlSeconds); } export async function cacheDelete(key: string): Promise<void> { await redis.del(key); } export async function cacheDeletePattern(pattern: string): Promise<void> { const keys = await redis.keys(pattern); if (keys.length > 0) { await redis.del(...keys); } } // Cache-aside pattern export async function cached<T>( key: string, ttl: number, fn: () => Promise<T> ): Promise<T> { const hit = await cacheGet<T>(key); if (hit) return hit; const value = await fn(); await cacheSet(key, value, ttl); return value; } ``` ### Usage in Services ```typescript // src/services/user.service.ts import { cached, cacheDelete } from '@utils/cache'; import { userRepository } from '@/database/repositories/user.repository'; export const getUserById = async (id: string) => { return cached(`user:${id}`, 300, async () => { const user = await userRepository.findById(id); if (!user) throw new NotFoundError('User'); const { password, ...withoutPassword } = user; return withoutPassword; }); }; export const updateUser = async (id: string, data: UpdateUserDto) => { const user = await userRepository.update(id, data); // Invalidate cache after update await cacheDelete(`user:${id}`); const { password, ...withoutPassword } = user; return withoutPassword; }; ``` ### Cache Key Conventions ```typescript // ✅ CORRECT: Namespaced keys const keys = { user: (id: string) => `user:${id}`,
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기