CI/CD Pipelines
Automate your APSO backend deployments with continuous integration and deployment pipelines.
GitHub Actions
Basic Deployment
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
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 linter
run: npm run lint
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to APSO Cloud
run: |
npm install -g @apso/cli
apso deploy --token ${{ secrets.APSO_TOKEN }}Docker Build and Push
# .github/workflows/docker.yml
name: Docker Build
on:
push:
branches: [main]
tags: ['v*']
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Login 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=ref,event=branch
type=semver,pattern={{version}}
type=sha
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}Multi-Environment Deployment
# .github/workflows/deploy-environments.yml
name: Deploy to Environments
on:
push:
branches:
- main
- staging
- develop
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set environment
id: env
run: |
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
echo "environment=production" >> $GITHUB_OUTPUT
echo "url=https://api.yourapp.com" >> $GITHUB_OUTPUT
elif [ "${{ github.ref }}" == "refs/heads/staging" ]; then
echo "environment=staging" >> $GITHUB_OUTPUT
echo "url=https://staging-api.yourapp.com" >> $GITHUB_OUTPUT
else
echo "environment=development" >> $GITHUB_OUTPUT
echo "url=https://dev-api.yourapp.com" >> $GITHUB_OUTPUT
fi
- name: Deploy
run: |
npm install -g @apso/cli
apso deploy --environment ${{ steps.env.outputs.environment }}
env:
APSO_TOKEN: ${{ secrets.APSO_TOKEN }}
- name: Verify deployment
run: |
curl -f ${{ steps.env.outputs.url }}/health || exit 1Database Migrations
# .github/workflows/migrate.yml
name: Run Migrations
on:
workflow_dispatch:
push:
branches: [main]
paths:
- 'src/migrations/**'
jobs:
migrate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Run migrations
run: npm run migration:run
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}GitLab CI
# .gitlab-ci.yml
stages:
- test
- build
- deploy
variables:
DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
test:
stage: test
image: node:20
script:
- npm ci
- npm test
- npm run lint
cache:
paths:
- node_modules/
build:
stage: build
image: docker:latest
services:
- docker:dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $DOCKER_IMAGE .
- docker push $DOCKER_IMAGE
deploy_staging:
stage: deploy
image: alpine
script:
- apk add --no-cache curl
- curl -X POST "$DEPLOY_WEBHOOK_URL" -d "image=$DOCKER_IMAGE"
environment:
name: staging
url: https://staging-api.yourapp.com
only:
- develop
deploy_production:
stage: deploy
image: alpine
script:
- apk add --no-cache curl
- curl -X POST "$DEPLOY_WEBHOOK_URL" -d "image=$DOCKER_IMAGE"
environment:
name: production
url: https://api.yourapp.com
when: manual
only:
- mainCircleCI
# .circleci/config.yml
version: 2.1
orbs:
node: circleci/node@5
docker: circleci/docker@2
jobs:
test:
docker:
- image: cimg/node:20.0
steps:
- checkout
- node/install-packages
- run:
name: Run tests
command: npm test
- run:
name: Run linter
command: npm run lint
build-and-push:
executor: docker/docker
steps:
- setup_remote_docker
- checkout
- docker/check
- docker/build:
image: $DOCKER_USERNAME/apso-api
tag: ${CIRCLE_SHA1}
- docker/push:
image: $DOCKER_USERNAME/apso-api
tag: ${CIRCLE_SHA1}
deploy:
docker:
- image: cimg/base:current
steps:
- run:
name: Deploy to production
command: |
curl -X POST "$DEPLOY_WEBHOOK" \
-H "Authorization: Bearer $DEPLOY_TOKEN" \
-d "image=$DOCKER_USERNAME/apso-api:${CIRCLE_SHA1}"
workflows:
build-deploy:
jobs:
- test
- build-and-push:
requires:
- test
filters:
branches:
only: main
- deploy:
requires:
- build-and-push
filters:
branches:
only: mainAWS CodePipeline
# buildspec.yml
version: 0.2
phases:
install:
runtime-versions:
nodejs: 20
commands:
- npm ci
pre_build:
commands:
- npm test
- npm run lint
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $ECR_REGISTRY
build:
commands:
- docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$CODEBUILD_RESOLVED_SOURCE_VERSION .
- docker push $ECR_REGISTRY/$ECR_REPOSITORY:$CODEBUILD_RESOLVED_SOURCE_VERSION
post_build:
commands:
- printf '[{"name":"apso-api","imageUri":"%s"}]' $ECR_REGISTRY/$ECR_REPOSITORY:$CODEBUILD_RESOLVED_SOURCE_VERSION > imagedefinitions.json
artifacts:
files: imagedefinitions.jsonDeployment Strategies
Rolling Deployment
# Kubernetes rolling update
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0Blue-Green Deployment
# Switch traffic after verification
- name: Deploy green
run: kubectl apply -f k8s/deployment-green.yaml
- name: Verify green
run: |
kubectl wait --for=condition=ready pod -l app=apso-green
curl -f https://green.yourapp.com/health
- name: Switch traffic
run: kubectl patch service apso -p '{"spec":{"selector":{"version":"green"}}}'
- name: Remove blue
run: kubectl delete deployment apso-blueCanary Deployment
# Gradual traffic shift
- name: Deploy canary (10%)
run: |
kubectl apply -f k8s/deployment-canary.yaml
kubectl patch virtualservice apso -p '{"spec":{"http":[{"route":[{"destination":{"host":"apso-stable"},"weight":90},{"destination":{"host":"apso-canary"},"weight":10}]}]}}'
- name: Monitor canary
run: |
sleep 300
# Check error rates and latency
- name: Promote or rollback
run: |
if [ "$CANARY_SUCCESS" == "true" ]; then
kubectl patch virtualservice apso -p '{"spec":{"http":[{"route":[{"destination":{"host":"apso-canary"},"weight":100}]}]}}'
else
kubectl delete deployment apso-canary
fiSecrets Management
GitHub Secrets
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
JWT_SECRET: ${{ secrets.JWT_SECRET }}
APSO_TOKEN: ${{ secrets.APSO_TOKEN }}External Secrets Operator
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: apso-secrets
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: apso-secrets
data:
- secretKey: database-url
remoteRef:
key: apso/production
property: DATABASE_URLRelated
Last updated on