diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..203ce080 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,43 @@ +# Dependencies +node_modules + +# Git +.git +.gitignore + +# Environment files +.env +.env.* + +# Documentation +*.md +README.md + +# IDE/Editor +.vscode +.idea +*.swp +*.swo + +# Config files +.eslintrc.js +.editorconfig +.prettierrc + +# Example and test files +example +images +test +tests +__tests__ + +# Build artifacts (will be created in builder stage) +dist + +# Logs +*.log +npm-debug.log* + +# OS files +.DS_Store +Thumbs.db diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000..6dc8e639 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,29 @@ +name: CI Build + +on: + push: + branches: + - main + - master + paths: + - 'Dockerfile' + - 'src/**' + - 'package.json' + - 'package-lock.json' + pull_request: + branches: + - main + - master + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + uses: Buzzvil/workflows/.github/workflows/build_ecr.yaml@main + secrets: inherit + with: + repository: devtools-remote-debugger + dockerfile: Dockerfile + tags: ${{ github.sha }},latest diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 00000000..b2091af8 --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -0,0 +1,113 @@ +name: Deploy to ECS + +on: + workflow_dispatch: + inputs: + image_tag: + description: 'Image tag to deploy (default: latest commit SHA)' + required: false + default: '' + push: + branches: + - main + - master + paths: + - 'Dockerfile' + - 'src/**' + - 'package.json' + - 'package-lock.json' + +concurrency: + group: deploy-${{ github.ref }} + cancel-in-progress: false + +env: + AWS_REGION: ap-northeast-1 + ECR_REPOSITORY: devtools-remote-debugger + ECS_CLUSTER: devtools-remote-debugger + ECS_SERVICE: devtools-remote-debugger + TASK_DEFINITION_FAMILY: devtools-remote-debugger + CONTAINER_NAME: devtools-remote-debugger + +jobs: + build: + uses: Buzzvil/workflows/.github/workflows/build_ecr.yaml@main + secrets: inherit + with: + repository: devtools-remote-debugger + dockerfile: Dockerfile + tags: ${{ github.sha }},latest + + deploy: + name: Deploy to ECS + needs: build + runs-on: self-hosted + steps: + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: ${{ env.AWS_REGION }} + + - name: Determine image tag + id: image-tag + run: | + if [ -n "${{ github.event.inputs.image_tag }}" ]; then + echo "tag=${{ github.event.inputs.image_tag }}" >> $GITHUB_OUTPUT + else + echo "tag=${{ github.sha }}" >> $GITHUB_OUTPUT + fi + + - name: Download current task definition + run: | + aws ecs describe-task-definition \ + --task-definition ${{ env.TASK_DEFINITION_FAMILY }} \ + --query taskDefinition > task-definition.json + + - name: Update task definition with new image + id: task-def + uses: aws-actions/amazon-ecs-render-task-definition@v1 + with: + task-definition: task-definition.json + container-name: ${{ env.CONTAINER_NAME }} + image: 591756927972.dkr.ecr.${{ env.AWS_REGION }}.amazonaws.com/${{ env.ECR_REPOSITORY }}:${{ steps.image-tag.outputs.tag }} + + - name: Register new task definition + id: register-task-def + run: | + # Remove fields that can't be in RegisterTaskDefinition + jq 'del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)' \ + ${{ steps.task-def.outputs.task-definition }} > cleaned-task-def.json + + TASK_DEF_ARN=$(aws ecs register-task-definition \ + --cli-input-json file://cleaned-task-def.json \ + --query 'taskDefinition.taskDefinitionArn' \ + --output text) + echo "task-definition-arn=$TASK_DEF_ARN" >> $GITHUB_OUTPUT + + - name: Update ECS service + run: | + aws ecs update-service \ + --cluster ${{ env.ECS_CLUSTER }} \ + --service ${{ env.ECS_SERVICE }} \ + --task-definition ${{ steps.register-task-def.outputs.task-definition-arn }} \ + --force-new-deployment + + - name: Wait for service stability + run: | + echo "Waiting for ECS service to stabilize..." + aws ecs wait services-stable \ + --cluster ${{ env.ECS_CLUSTER }} \ + --services ${{ env.ECS_SERVICE }} + + - name: Deployment summary + run: | + echo "## Deployment Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Item | Value |" >> $GITHUB_STEP_SUMMARY + echo "|------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Cluster | ${{ env.ECS_CLUSTER }} |" >> $GITHUB_STEP_SUMMARY + echo "| Service | ${{ env.ECS_SERVICE }} |" >> $GITHUB_STEP_SUMMARY + echo "| Image Tag | ${{ steps.image-tag.outputs.tag }} |" >> $GITHUB_STEP_SUMMARY + echo "| Task Definition | ${{ steps.register-task-def.outputs.task-definition-arn }} |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Deployment completed successfully!" >> $GITHUB_STEP_SUMMARY diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..ba9ad009 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,45 @@ +# Stage 1: Build +FROM node:18-alpine AS builder + +WORKDIR /app + +# Install dependencies +COPY package.json package-lock.json ./ +RUN npm ci + +# Copy source and build +COPY . . +RUN npm run build + +# Stage 2: Production +FROM node:18-alpine AS production + +WORKDIR /app + +# Install production dependencies only +COPY package.json package-lock.json ./ +RUN npm ci --omit=dev && npm cache clean --force + +# Copy built assets and server files +COPY --from=builder /app/dist ./dist +COPY --from=builder /app/devtools-frontend ./devtools-frontend +COPY --from=builder /app/src/server ./src/server + +# Create non-root user +RUN addgroup -g 1001 -S nodejs && \ + adduser -S nodejs -u 1001 && \ + chown -R nodejs:nodejs /app + +USER nodejs + +# Environment +ENV NODE_ENV=production +ENV DEBUG_PORT=9876 + +# Health check +HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:9876/remote/debug/json || exit 1 + +EXPOSE 9876 + +CMD ["node", "src/server/index.js"]