Skip to main content

bunjs-production

Provides Bun.js production patterns — Docker, AWS ECS/Fargate, Redis caching, security hardening, CI/CD. Use when deploying or operationalizing a Bun.js service.

소스 정보

저장소
MadAppGang/magus
최근 소스 활동
2026년 9월 7일 04:11
감지된 SKILL.md 언어
영어
스타
10
포크
4

설치 방법

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

소스 파일 검토

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

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
bunjs-production
description
Provides Bun.js production patterns — Docker, AWS ECS/Fargate, Redis caching, security hardening, CI/CD. Use when deploying or operationalizing a Bun.js service.
user-invocable
false
disable-model-invocation
true
# 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:** - `${CLAUDE_PLUGIN_ROOT}/knowledge/backend/bunjs.md` - Core Bun patterns, HTTP servers, database access - `${CLAUDE_PLUGIN_ROOT}/skills/backend/bunjs-architecture/SKILL.md` - Layered architecture, camelCase conventions - `${CLAUDE_PLUGIN_ROOT}/knowledge/backend/bunjs-apidog.md` - 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
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기