Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
242 changes: 242 additions & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
# CD Pipeline - Build, Push, and Deploy
# Triggers on release or manual dispatch
name: CD

on:
release:
types: [published]
workflow_dispatch:
inputs:
environment:
description: 'Deployment environment'
required: true
default: 'staging'
type: choice
options:
- staging
- production
deploy_to:
description: 'Deployment target'
required: true
default: 'docker'
type: choice
options:
- docker
- fly
- railway
- render
- kubernetes

env:
REGISTRY: ghcr.io
BACKEND_IMAGE: ghcr.io/${{ github.repository_owner }}/finmind-backend
FRONTEND_IMAGE: ghcr.io/${{ github.repository_owner }}/finmind-frontend

permissions:
contents: read
packages: write
id-token: write

jobs:
build-and-push:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.meta.outputs.version }}
backend_digest: ${{ steps.build-backend.outputs.digest }}
frontend_digest: ${{ steps.build-frontend.outputs.digest }}

steps:
- uses: actions/checkout@v4

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

- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata for backend
id: meta-backend
uses: docker/metadata-action@v5
with:
images: ${{ env.BACKEND_IMAGE }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix=
type=raw,value=latest,enable={{is_default_branch}}

- name: Build and push backend
id: build-backend
uses: docker/build-push-action@v5
with:
context: ./packages/backend
file: ./packages/backend/Dockerfile
push: true
tags: ${{ steps.meta-backend.outputs.tags }}
labels: ${{ steps.meta-backend.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64

- name: Extract metadata for frontend
id: meta-frontend
uses: docker/metadata-action@v5
with:
images: ${{ env.FRONTEND_IMAGE }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix=
type=raw,value=latest,enable={{is_default_branch}}

- name: Build and push frontend
id: build-frontend
uses: docker/build-push-action@v5
with:
context: ./app
file: ./app/Dockerfile
push: true
tags: ${{ steps.meta-frontend.outputs.tags }}
labels: ${{ steps.meta-frontend.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64

- name: Generate version
id: meta
run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT

# Deploy to Fly.io
deploy-fly:
runs-on: ubuntu-latest
needs: build-and-push
if: github.event.inputs.deploy_to == 'fly'
environment: ${{ github.event.inputs.environment || 'staging' }}

steps:
- uses: actions/checkout@v4

- name: Install Flyctl
uses: superfly/flyctl-actions/setup-flyctl@master

- name: Deploy to Fly.io
run: |
cd deploy/fly
flyctl deploy --config fly.toml \
--image ${{ env.BACKEND_IMAGE }}:${{ needs.build-and-push.outputs.version || 'latest' }} \
--remote-only
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}

# Deploy to Railway
deploy-railway:
runs-on: ubuntu-latest
needs: build-and-push
if: github.event.inputs.deploy_to == 'railway'
environment: ${{ github.event.inputs.environment || 'staging' }}

steps:
- uses: actions/checkout@v4

- name: Install Railway CLI
run: npm install -g @railway/cli

- name: Deploy to Railway
run: railway up --detach
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}

# Deploy to Kubernetes
deploy-kubernetes:
runs-on: ubuntu-latest
needs: build-and-push
if: github.event.inputs.deploy_to == 'kubernetes'
environment: ${{ github.event.inputs.environment || 'staging' }}

steps:
- uses: actions/checkout@v4

- name: Set up kubectl
uses: azure/setup-kubectl@v4
with:
version: 'latest'

- name: Set up Helm
uses: azure/setup-helm@v4
with:
version: 'latest'

- name: Configure kubeconfig
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBECONFIG }}" | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config

- name: Add Bitnami repo
run: |
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update

- name: Deploy with Helm
run: |
cd deploy/helm/finmind
helm dependency update
helm upgrade --install finmind . \
--namespace finmind --create-namespace \
--set backend.image.tag=${{ needs.build-and-push.outputs.version || 'latest' }} \
--set secrets.jwtSecret=${{ secrets.JWT_SECRET }} \
--set postgresql.auth.password=${{ secrets.POSTGRES_PASSWORD }} \
--set ingress.hosts[0].host=${{ vars.API_HOST }} \
--wait --timeout 10m

# Update deployment manifests (GitOps)
update-manifests:
runs-on: ubuntu-latest
needs: build-and-push
if: github.event_name == 'release'

steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}

- name: Update image tags in K8s manifests
run: |
VERSION="${{ needs.build-and-push.outputs.version }}"
sed -i "s|ghcr.io/.*/finmind-backend:.*|${{ env.BACKEND_IMAGE }}:${VERSION}|g" deploy/k8s/app-stack.yaml
sed -i "s|ghcr.io/.*/finmind-frontend:.*|${{ env.FRONTEND_IMAGE }}:${VERSION}|g" deploy/k8s/app-stack.yaml

- name: Commit and push
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add deploy/k8s/
git diff --staged --quiet || git commit -m "chore: Update image tags to ${{ needs.build-and-push.outputs.version }}"
git push

# Security scan on release
security-scan:
runs-on: ubuntu-latest
needs: build-and-push
if: github.event_name == 'release'

steps:
- uses: actions/checkout@v4

- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: '${{ env.BACKEND_IMAGE }}:${{ needs.build-and-push.outputs.version }}'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'

- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: 'trivy-results.sarif'
104 changes: 104 additions & 0 deletions .github/workflows/docker-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Build and push Docker images on main branch pushes
name: Docker Build

on:
push:
branches: [main]
paths:
- 'packages/backend/**'
- 'app/**'
- 'Dockerfile*'
- 'docker-compose*.yml'
workflow_dispatch:

env:
REGISTRY: ghcr.io
BACKEND_IMAGE: ghcr.io/${{ github.repository_owner }}/finmind-backend
FRONTEND_IMAGE: ghcr.io/${{ github.repository_owner }}/finmind-frontend

permissions:
contents: read
packages: write

jobs:
changes:
runs-on: ubuntu-latest
outputs:
backend: ${{ steps.filter.outputs.backend }}
frontend: ${{ steps.filter.outputs.frontend }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
backend:
- 'packages/backend/**'
frontend:
- 'app/**'

build-backend:
needs: changes
if: needs.changes.outputs.backend == 'true' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

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

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push backend
uses: docker/build-push-action@v5
with:
context: ./packages/backend
file: ./packages/backend/Dockerfile
push: true
tags: |
${{ env.BACKEND_IMAGE }}:latest
${{ env.BACKEND_IMAGE }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64

build-frontend:
needs: changes
if: needs.changes.outputs.frontend == 'true' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

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

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push frontend
uses: docker/build-push-action@v5
with:
context: ./app
file: ./app/Dockerfile
push: true
tags: |
${{ env.FRONTEND_IMAGE }}:latest
${{ env.FRONTEND_IMAGE }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64
Loading