Skip to Content
🚀 APSO is now in public beta. Get started →

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 1

Database 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: - main

CircleCI

# .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: main

AWS 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.json

Deployment Strategies

Rolling Deployment

# Kubernetes rolling update spec: strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0

Blue-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-blue

Canary 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 fi

Secrets 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_URL
Last updated on