Continuous Integration (CI) en Continuous Deployment (CD) zijn essentiële praktijken in moderne software ontwikkeling:
GitHub Actions is de native CI/CD oplossing van GitHub:
Een GitHub Actions workflow bestaat uit:
.github/
└── workflows/
└── ci-cd.yml
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run tests
run: echo "Running tests..."
on)on:
push: # Bij elke push
pull_request: # Bij PR's
schedule: # Geplande runs
- cron: "0 2 * * *" # Elke dag om 2:00
workflow_dispatch: # Handmatige trigger
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
deploy:
needs: test # Wacht op test job
runs-on: ubuntu-latest
steps:
- run: echo "Deploying..."
In je GitHub repository:
DOCKER_HUB_USERNAME: je Docker Hub usernameDOCKER_HUB_ACCESS_TOKEN: je access token
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: $
password: $
name: Build and Push Docker Image
on:
push:
branches: [main]
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: $
password: $
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: |
$/myapp:latest
$/myapp:$
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: |
$/myapp:latest
$/myapp:$
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: $/myapp
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha
type=raw,value=latest,enable=
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: $
labels: $
Voor deployment naar productie servers:
# GitHub Secrets toevoegen:
# SERVER_HOST: je server IP/hostname
# SERVER_USER: SSH username
# SSH_PRIVATE_KEY: private SSH key
deploy-to-production:
needs: build-and-push
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to server
uses: appleboy/ssh-action@v1.0.0
with:
host: $
username: $
key: $
script: |
docker pull $/myapp:latest
docker stop myapp || true
docker rm myapp || true
docker run -d --name myapp -p 80:3000 \
$/myapp:latest
Voor applicaties met meerdere services:
deploy-compose:
needs: build-and-push
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Deploy with Docker Compose
uses: appleboy/ssh-action@v1.0.0
with:
host: $
username: $
key: $
script: |
cd /opt/myapp
git pull origin main
docker-compose pull
docker-compose up -d --force-recreate
deploy-to-staging:
needs: build-and-push
runs-on: ubuntu-latest
environment: staging
steps:
- name: Deploy to staging
uses: appleboy/ssh-action@v1.0.0
with:
host: $
username: $
key: $
script: |
cd /opt/myapp-staging
docker-compose -f docker-compose.staging.yml pull
docker-compose -f docker-compose.staging.yml up -d
deploy-to-production:
needs: [build-and-push, deploy-to-staging]
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy to production
# ... productie deployment
deploy-to-k8s:
needs: build-and-push
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure kubectl
uses: azure/k8s-set-context@v3
with:
method: kubeconfig
kubeconfig: $
- name: Deploy to Kubernetes
run: |
# Update image tag in deployment
sed -i "s|image: myapp:.*|image: $/myapp:$|" k8s/deployment.yaml
# Apply manifests
kubectl apply -f k8s/
# Wait for rollout
kubectl rollout status deployment/myapp
deploy-with-helm:
needs: build-and-push
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure kubectl
uses: azure/k8s-set-context@v3
with:
method: kubeconfig
kubeconfig: $
- name: Install Helm
uses: azure/setup-helm@v3
with:
version: "v3.12.0"
- name: Deploy with Helm
run: |
helm upgrade --install myapp ./helm-chart \
--set image.repository=$/myapp \
--set image.tag=$ \
--namespace production \
--create-namespace \
--wait
Voor GitOps workflows met ArgoCD:
update-gitops-repo:
needs: build-and-push
runs-on: ubuntu-latest
steps:
- name: Checkout GitOps repo
uses: actions/checkout@v4
with:
repository: myorg/gitops-repo
token: $
- name: Update image tag
run: |
# Update Helm values file
sed -i "s|tag: .*|tag: $|" environments/production/values.yaml
# Commit and push
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git add .
git commit -m "Update image tag to $"
git push
name: Complete CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests
run: |
npm install
npm test
npm run lint
build:
needs: test
runs-on: ubuntu-latest
if: github.event_name == 'push'
outputs:
image-tag: $
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: $
password: $
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: $/myapp
tags: |
type=ref,event=branch
type=sha
type=raw,value=latest,enable=
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: $
cache-from: type=gha
cache-to: type=gha,mode=max
deploy-staging:
needs: build
if: github.ref == 'refs/heads/develop'
runs-on: ubuntu-latest
environment: staging
steps:
- name: Deploy to staging
# ... staging deployment
deploy-production:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy to production
# ... production deployment
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: $/myapp:$
format: "sarif"
output: "trivy-results.sarif"
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: "trivy-results.sarif"
test-matrix:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16, 18, 20]
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v4
- name: Setup Node.js $
uses: actions/setup-node@v4
with:
node-version: $
- run: npm test
# Voorbeeld: Beveiligde workflow
- name: Login to registry
uses: docker/login-action@v3
with:
username: $
password: $
- name: Build with security scan
run: |
docker build -t myapp .
trivy image myapp
# Cache dependencies
- name: Cache Docker layers
uses: actions/cache@v3
with:
path: /tmp/.buildx-cache
key: $-buildx-$
restore-keys: |
$-buildx-
- name: Notify on failure
if: failure()
uses: 8398a7/action-slack@v3
with:
status: failure
channel: "#deployments"
env:
SLACK_WEBHOOK_URL: $
Repository Settings configureren:
production:
environment:
name: production
url: https://myapp.com
needs: build
if: github.ref == 'refs/heads/main'
name: Node.js CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
NODE_VERSION: "18"
REGISTRY: docker.io
IMAGE_NAME: myusername/myapp
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: $
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Run tests
run: |
npm run test:unit
npm run test:integration
- name: Upload coverage
uses: codecov/codecov-action@v3
build-and-push:
needs: test
runs-on: ubuntu-latest
if: github.event_name == 'push'
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
registry: $
username: $
password: $
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: $/$
tags: |
type=ref,event=branch
type=sha,prefix=-
type=raw,value=latest,enable=
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: $
labels: $
cache-from: type=gha
cache-to: type=gha,mode=max
deploy-staging:
needs: build-and-push
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/develop'
environment: staging
steps:
- name: Deploy to staging
uses: appleboy/ssh-action@v1.0.0
with:
host: $
username: $
key: $
script: |
docker pull $/$:develop-$
docker stop myapp-staging || true
docker rm myapp-staging || true
docker run -d --name myapp-staging \
-p 3001:3000 \
-e NODE_ENV=staging \
$/$:develop-$
deploy-production:
needs: build-and-push
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment: production
steps:
- name: Deploy to production
uses: appleboy/ssh-action@v1.0.0
with:
host: $
username: $
key: $
script: |
docker pull $/$:latest
docker stop myapp || true
docker rm myapp || true
docker run -d --name myapp \
-p 80:3000 \
-e NODE_ENV=production \
$/$:latest
# Health check
sleep 10
curl -f http://localhost/health || exit 1
Deze tutorial biedt een complete gids voor het opzetten van professionele CI/CD pipelines met GitHub Actions, van eenvoudige builds tot complexe multi-environment deployments met Kubernetes en Helm.