DevToolBoxFREE
BlogAdvertise

GitHub Actions + Docker CI/CD: Build, Push och Deploy

13 minby DevToolBox

En robust CI/CD-pipeline med GitHub Actions och Docker automatiserar hela deploymentlivscykeln. Den här guiden bygger en produktionsklar pipeline med multi-platform builds och zero-downtime deployments.

Pipeline-översikt

Pipeline har fyra steg: CI (test), Build (Docker-image), Push (till registry) och Deploy (till server).

Pipeline Flow:
  git push → GitHub Actions triggered
       │
       ├─ [Job 1] CI: npm test, lint, type-check
       │         (runs on all PRs and main pushes)
       │
       ├─ [Job 2] Build: docker build --platform linux/amd64,linux/arm64
       │         (runs only on main branch push)
       │         Push to ghcr.io/org/app:sha-abc1234
       │
       └─ [Job 3] Deploy: SSH to server, docker pull + restart
                 (runs after successful build)
                 Zero-downtime with blue-green or rolling restart

Dockerfile best practices

En välstrukturerad Dockerfile är grunden. Multi-stage builds minskar avsevärt den slutliga imagestorleken.

# Multi-stage Dockerfile (Node.js example)

# Stage 1: Dependencies
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && cp -R node_modules /prod_node_modules
RUN npm ci   # install ALL deps for building

# Stage 2: Builder
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

# Stage 3: Production runner (minimal image)
FROM node:20-alpine AS runner
WORKDIR /app

# Security: run as non-root user
RUN addgroup --system --gid 1001 nodejs \
 && adduser --system --uid 1001 appuser

# Copy only what's needed for runtime
COPY --from=deps /prod_node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./

USER appuser
EXPOSE 3000

# Health check for orchestrators
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
  CMD wget -qO- http://localhost:3000/health || exit 1

CMD ["node", "dist/index.js"]

# Result: ~150MB instead of ~800MB for the same app

Grundläggande CI/CD-pipeline

Det här arbetsflödet bygger och pushar till GitHub Container Registry vid varje push till main.

# .github/workflows/deploy.yml

name: CI/CD Pipeline

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

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  # Stage 1: Test
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Run lint
        run: npm run lint

  # Stage 2: Build and Push Docker image
  build-push:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    permissions:
      contents: read
      packages: write

    outputs:
      image-digest: ${{ steps.build.outputs.digest }}

    steps:
      - uses: actions/checkout@v4

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}  # Auto-provided by GitHub

      - name: Extract Docker metadata (tags, labels)
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=sha,prefix=sha-
            type=raw,value=latest,enable={{is_default_branch}}

      - name: Build and push Docker image
        id: build
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  # Stage 3: Deploy to server
  deploy:
    needs: build-push
    runs-on: ubuntu-latest
    environment: production  # GitHub Environment with protection rules

    steps:
      - name: Deploy to production server via SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            # Pull latest image
            echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
            docker pull ghcr.io/${{ github.repository }}:latest

            # Zero-downtime restart
            docker compose -f /app/docker-compose.prod.yml up -d --no-deps app

            # Verify deployment
            sleep 10
            docker compose -f /app/docker-compose.prod.yml ps

Avancerade pipeline-funktioner

Produktionspipelines behöver extra funktioner: multi-platform builds, säkerhetsscanning och rollback.

# Advanced Pipeline Features

# 1. Multi-platform builds (AMD64 + ARM64)
- name: Set up QEMU
  uses: docker/setup-qemu-action@v3

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

- name: Build multi-platform image
  uses: docker/build-push-action@v5
  with:
    platforms: linux/amd64,linux/arm64
    push: true
    tags: ${{ steps.meta.outputs.tags }}

# 2. Security scanning with Trivy
- name: Scan image for vulnerabilities
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
    format: 'sarif'
    output: 'trivy-results.sarif'
    severity: 'CRITICAL,HIGH'
    exit-code: '1'  # Fail pipeline on critical vulnerabilities

# 3. Semantic versioning with tags
on:
  push:
    tags:
      - 'v*.*.*'

jobs:
  release:
    steps:
      - name: Extract version from tag
        id: version
        run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT

      - uses: docker/build-push-action@v5
        with:
          tags: |
            ghcr.io/myorg/myapp:${{ steps.version.outputs.VERSION }}
            ghcr.io/myorg/myapp:latest

# 4. Rollback on failure
- name: Deploy with rollback
  run: |
    PREVIOUS_IMAGE=$(docker inspect --format='{{.Config.Image}}' app-container)
    docker pull $NEW_IMAGE

    if docker run --rm $NEW_IMAGE node -e "require('./dist/index.js')"; then
      docker stop app-container
      docker run -d --name app-container $NEW_IMAGE
    else
      echo "New image failed healthcheck, keeping previous"
      exit 1
    fi

Vanliga frågor

Hur lagrar jag secrets i GitHub Actions?

Gå till repository-inställningar → Secrets and variables → Actions → New repository secret.

Skillnad mellan ghcr.io och Docker Hub?

GitHub Container Registry är gratis för offentliga repositories och integrerat med GitHub-behörigheter.

Hur bygger jag för AMD64 och ARM64?

Använd docker/setup-qemu-action och docker/setup-buildx-action.

Hur implementerar jag miljöspecifika deployments?

Använd GitHub Environments för staging och produktion.

Relaterade verktyg

Var detta hjälpsamt?

Stay Updated

Get weekly dev tips and new tool announcements.

No spam. Unsubscribe anytime.

Partner Picks

Sponsor this article

Place your product next to this developer topic with tracked clicks.

Ask about article sponsorship

This site uses cookies for analytics and to display ads. By continuing to browse, you agree. Privacy Policy