devops-infra-github

📁 lcaparros/cicd-github-bun 📅 7 days ago
1
总安装量
1
周安装量
#49613
全站排名
安装命令
npx skills add https://github.com/lcaparros/cicd-github-bun --skill devops-infra-github

Agent 安装分布

opencode 1

Skill 文档

DevOps & Infrastructure Skill

Overview

Expert guidance for containerization, orchestration, and CI/CD pipelines for Bun monorepo projects.

When to Use This Skill

  • Creating Docker images for services
  • Setting up Docker Compose for local development
  • Configuring GitHub Actions for CI/CD
  • Managing environment variables and secrets
  • Automating deployments

Stack Context

  • Runtime: Bun
  • Containerization: Docker (multi-stage builds with oven/bun:alpine)
  • Orchestration: Docker Compose
  • CI/CD: GitHub Actions
  • Version Management: Changesets
  • Registry: Docker Hub / GitHub Container Registry

Project Structure

.
├── .github/
│   └── workflows/
│       ├── service-a-ci.yml
│       └── service-b-ci.yml
├── packages/
│   ├── service-a/
│   │   ├── Dockerfile
│   │   ├── .dockerignore
│   │   └── docker-compose.yml
│   └── service-b/
│       ├── Dockerfile
│       ├── .dockerignore
│       └── docker-compose.yml
├── .changeset/
└── package.json

Workflows

1. Dockerfile for Bun Service

packages/my-service/Dockerfile:

# Multi-stage build for optimized image size
FROM oven/bun:alpine AS base
WORKDIR /app

# Install dependencies stage
FROM base AS deps
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile --production

# Build stage (if needed)
FROM base AS builder
COPY package.json bun.lockb ./
COPY src ./src
RUN bun install --frozen-lockfile

# Production stage
FROM base AS runner
WORKDIR /app

# Copy dependencies from deps stage
COPY --from=deps /app/node_modules ./node_modules
COPY package.json ./
COPY src ./src

# Create non-root user
RUN addgroup -g 1001 -S bunuser && \
    adduser -S bunuser -u 1001
USER bunuser

# Expose port
EXPOSE 3000

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

# Start application
CMD ["bun", "src/index.js"]

.dockerignore:

node_modules
.env
.env.*
*.log
.git
.gitignore
README.md
CHANGELOG.md
.changeset
dist
coverage
.vscode
.idea

2. Docker Compose for Local Development

packages/my-service/docker-compose.yml:

version: '3.9'

services:
  # Main service
  api:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: my-service-api
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
      - PORT=3000
      - MONGO_URI=mongodb://mongo:27017
      - DB_NAME=mydb
      - REDIS_URI=redis://redis:6379
    depends_on:
      mongo:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - app-network
    restart: unless-stopped
    volumes:
      # Hot reload for development
      - ./src:/app/src:ro
      - ./package.json:/app/package.json:ro

  # MongoDB
  mongo:
    image: mongo:7-jammy
    container_name: my-service-mongo
    ports:
      - "27017:27017"
    environment:
      - MONGO_INITDB_DATABASE=mydb
    volumes:
      - mongo-data:/data/db
      - ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro
    networks:
      - app-network
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s

  # Redis
  redis:
    image: redis:7-alpine
    container_name: my-service-redis
    ports:
      - "6379:6379"
    command: redis-server --appendonly yes
    volumes:
      - redis-data:/data
    networks:
      - app-network
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 5
      start_period: 5s

volumes:
  mongo-data:
    driver: local
  redis-data:
    driver: local

networks:
  app-network:
    driver: bridge

Usage:

# Start all services
docker compose up -d

# View logs
docker compose logs -f

# Stop services
docker compose down

# Rebuild and start
docker compose up --build -d

# Remove volumes (clean state)
docker compose down -v

3. MongoDB Initialization Script

packages/my-service/mongo-init.js:

// Run on first container start
db = db.getSiblingDB('mydb')

// Create collections
db.createCollection('users')
db.createCollection('posts')

// Create indexes
db.users.createIndex({ email: 1 }, { unique: true })
db.posts.createIndex({ createdAt: -1 })

// Insert seed data
db.users.insertMany([
  {
    name: 'Admin User',
    email: 'admin@example.com',
    createdAt: new Date()
  }
])

print('MongoDB initialized successfully')

4. GitHub Actions CI/CD Pipeline

.github/workflows/service-a-ci.yml:

name: Service A CI/CD

on:
  push:
    branches: [main, develop]
    paths:
      - 'packages/service-a/**'
      - '.changeset/**'
  pull_request:
    branches: [main]
    paths:
      - 'packages/service-a/**'

env:
  SERVICE_NAME: service-a
  SERVICE_PATH: packages/service-a
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}/service-a

jobs:
  # Lint and test
  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 --frozen-lockfile
        working-directory: ${{ env.SERVICE_PATH }}
      
      - name: Lint
        run: bun run lint
        working-directory: ${{ env.SERVICE_PATH }}
      
      - name: Test
        run: bun test
        working-directory: ${{ env.SERVICE_PATH }}

  # Check for version changes
  version-check:
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    outputs:
      has-changeset: ${{ steps.changeset.outputs.hasChangesets }}
      version: ${{ steps.get-version.outputs.version }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      
      - name: Setup Bun
        uses: oven-sh/setup-bun@v1
      
      - name: Install dependencies
        run: bun install --frozen-lockfile
      
      - name: Check for changesets
        id: changeset
        uses: changesets/action@v1
        with:
          version: bun run version
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      
      - name: Get package version
        id: get-version
        run: |
          VERSION=$(node -p "require('./package.json').version")
          echo "version=$VERSION" >> $GITHUB_OUTPUT
        working-directory: ${{ env.SERVICE_PATH }}

  # Build and push Docker image
  build-and-push:
    needs: [test, version-check]
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      
      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      
      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=semver,pattern={{version}},value=${{ needs.version-check.outputs.version }}
            type=semver,pattern={{major}}.{{minor}},value=${{ needs.version-check.outputs.version }}
            type=raw,value=latest,enable={{is_default_branch}}
            type=sha,prefix={{branch}}-
      
      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: ${{ env.SERVICE_PATH }}
          file: ${{ env.SERVICE_PATH }}/Dockerfile
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

  # Create GitHub Release
  release:
    needs: [version-check, build-and-push]
    if: needs.version-check.outputs.has-changeset == 'true'
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4
      
      - name: Create Release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ env.SERVICE_NAME }}@v${{ needs.version-check.outputs.version }}
          release_name: ${{ env.SERVICE_NAME }} v${{ needs.version-check.outputs.version }}
          body_path: ${{ env.SERVICE_PATH }}/CHANGELOG.md
          draft: false
          prerelease: false

  # Deploy (placeholder)
  deploy:
    needs: [build-and-push]
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to Production
        run: |
          echo "Deployment step - integrate with your deployment platform"
          echo "Image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.version-check.outputs.version }}"
          # Add deployment commands here (e.g., kubectl, SSH, etc.)

5. Multi-Service CI/CD with Matrix

.github/workflows/monorepo-ci.yml:

name: Monorepo CI/CD

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  # Detect changed services
  detect-changes:
    runs-on: ubuntu-latest
    outputs:
      services: ${{ steps.filter.outputs.changes }}
    steps:
      - uses: actions/checkout@v4
      
      - uses: dorny/paths-filter@v2
        id: filter
        with:
          filters: |
            service-a:
              - 'packages/service-a/**'
            service-b:
              - 'packages/service-b/**'
            web:
              - 'packages/web/**'

  # Test changed services
  test:
    needs: detect-changes
    if: ${{ needs.detect-changes.outputs.services != '[]' }}
    runs-on: ubuntu-latest
    strategy:
      matrix:
        service: ${{ fromJSON(needs.detect-changes.outputs.services) }}
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Bun
        uses: oven-sh/setup-bun@v1
      
      - name: Install dependencies
        run: bun install --frozen-lockfile
      
      - name: Test ${{ matrix.service }}
        run: |
          cd packages/${{ matrix.service }}
          bun run lint
          bun test

6. Environment Variables Management

Development (.env):

# Service
PORT=3000
NODE_ENV=development

# MongoDB
MONGO_URI=mongodb://localhost:27017
DB_NAME=mydb_dev

# Redis
REDIS_URI=redis://localhost:6379

# Auth
API_TOKEN=dev-token-12345

Production (GitHub Secrets):

  • MONGO_URI: Production MongoDB connection string
  • REDIS_URI: Production Redis connection string
  • API_TOKEN: Production API token
  • DOCKER_USERNAME: Docker registry username
  • DOCKER_PASSWORD: Docker registry password

Using secrets in GitHub Actions:

- name: Deploy
  env:
    MONGO_URI: ${{ secrets.MONGO_URI }}
    REDIS_URI: ${{ secrets.REDIS_URI }}
    API_TOKEN: ${{ secrets.API_TOKEN }}

7. Docker Build Optimization

Multi-architecture builds:

- name: Set up QEMU
  uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
  uses: docker/setup-buildx-action@v3

- name: Build and push
  uses: docker/build-push-action@v5
  with:
    platforms: linux/amd64,linux/arm64
    push: true
    tags: ${{ steps.meta.outputs.tags }}
    cache-from: type=gha
    cache-to: type=gha,mode=max

8. Docker Compose for Production

docker-compose.prod.yml:

version: '3.9'

services:
  api:
    image: ghcr.io/org/service-a:latest
    container_name: service-a-api
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - PORT=3000
    env_file:
      - .env.production
    depends_on:
      - mongo
      - redis
    networks:
      - app-network
    restart: always
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 256M

  mongo:
    image: mongo:7-jammy
    container_name: service-a-mongo
    environment:
      - MONGO_INITDB_ROOT_USERNAME=${MONGO_USER}
      - MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD}
    volumes:
      - mongo-data:/data/db
    networks:
      - app-network
    restart: always

  redis:
    image: redis:7-alpine
    container_name: service-a-redis
    command: redis-server --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis-data:/data
    networks:
      - app-network
    restart: always

volumes:
  mongo-data:
  redis-data:

networks:
  app-network:
    driver: bridge

9. Deployment Script

deploy.sh:

#!/bin/bash
set -e

SERVICE_NAME=$1
VERSION=$2

if [ -z "$SERVICE_NAME" ] || [ -z "$VERSION" ]; then
  echo "Usage: ./deploy.sh <service-name> <version>"
  exit 1
fi

echo "Deploying $SERVICE_NAME version $VERSION..."

# Pull latest image
docker pull ghcr.io/org/$SERVICE_NAME:$VERSION

# Stop current container
docker compose -f packages/$SERVICE_NAME/docker-compose.prod.yml down

# Start new version
docker compose -f packages/$SERVICE_NAME/docker-compose.prod.yml up -d

# Check health
sleep 5
if curl -f http://localhost:3000/health; then
  echo "✅ Deployment successful"
else
  echo "❌ Health check failed"
  exit 1
fi

10. Monitoring and Logging

Docker logging configuration:

services:
  api:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

View logs:

# Follow logs for all services
docker compose logs -f

# Follow logs for specific service
docker compose logs -f api

# View last 100 lines
docker compose logs --tail=100 api

Best Practices

  1. Docker:

    • Use multi-stage builds to reduce image size
    • Use oven/bun:alpine for smallest images
    • Run as non-root user
    • Include health checks
    • Use .dockerignore effectively
  2. Docker Compose:

    • Use health checks for dependencies
    • Define restart policies
    • Use volumes for persistent data
    • Network isolation with custom networks
    • Set resource limits in production
  3. CI/CD:

    • Run tests before building
    • Use path filters to only build changed services
    • Tag images with semantic versions
    • Cache dependencies for faster builds
    • Use GitHub Secrets for sensitive data
  4. Versioning:

    • Use Changesets for version management
    • Automate version bumps on merge to main
    • Create GitHub Releases automatically
    • Tag format: service-name@vX.X.X
  5. Secrets:

    • Never commit .env files
    • Use GitHub Secrets for CI/CD
    • Use environment-specific .env files
    • Rotate secrets regularly
  6. Deployment:

    • Use health checks before routing traffic
    • Implement zero-downtime deployments
    • Keep rollback capability
    • Monitor logs and metrics

Common Commands

Docker:

# Build image
docker build -t my-service:latest .

# Run container
docker run -p 3000:3000 my-service:latest

# View running containers
docker ps

# Stop container
docker stop <container-id>

# Remove container
docker rm <container-id>

# View logs
docker logs -f <container-id>

Docker Compose:

# Start services
docker compose up -d

# Stop services
docker compose down

# Rebuild services
docker compose build

# View logs
docker compose logs -f

# Execute command in container
docker compose exec api bun run migrate

References