Skip to content

Retry EC2 deployment #11

Retry EC2 deployment

Retry EC2 deployment #11

Workflow file for this run

name: CD
on:
push:
branches: [main]
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_PREFIX: ${{ vars.IMAGE_PREFIX || 'ghcr.io/shikhar-395/exchange' }}
jobs:
changes:
runs-on: ubuntu-latest
outputs:
backend: ${{ steps.filter.outputs.backend }}
engine: ${{ steps.filter.outputs.engine }}
db-processor: ${{ steps.filter.outputs.db-processor }}
websocket: ${{ steps.filter.outputs.websocket }}
market-maker: ${{ steps.filter.outputs.market-maker }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
backend:
- '.github/workflows/cd.yml'
- 'apps/backend/**'
- 'packages/database/**'
- 'packages/common/**'
- 'packages/email/**'
- 'docker/backend/**'
- 'docker/compose-files/docker-compose-deploy.yml'
engine:
- '.github/workflows/cd.yml'
- 'apps/engine/**'
- 'packages/database/**'
- 'packages/common/**'
- 'docker/engine/**'
- 'docker/compose-files/docker-compose-deploy.yml'
db-processor:
- '.github/workflows/cd.yml'
- 'apps/db-processor/**'
- 'packages/common/**'
- 'packages/database/**'
- 'docker/db-processor/**'
- 'docker/compose-files/docker-compose-deploy.yml'
websocket:
- '.github/workflows/cd.yml'
- 'apps/websocket/**'
- 'packages/common/**'
- 'docker/websocket/**'
- 'docker/compose-files/docker-compose-deploy.yml'
market-maker:
- '.github/workflows/cd.yml'
- 'apps/market-maker/**'
- 'docker/market-maker/**'
- 'docker/compose-files/docker-compose-deploy.yml'
build-backend:
needs: changes
if: needs.changes.outputs.backend == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push backend
uses: docker/build-push-action@v6
with:
context: .
file: docker/backend/Dockerfile
push: true
tags: |
${{ env.IMAGE_PREFIX }}/backend:${{ github.sha }}
${{ env.IMAGE_PREFIX }}/backend:latest
build-engine:
needs: changes
if: needs.changes.outputs.engine == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push engine
uses: docker/build-push-action@v6
with:
context: .
file: docker/engine/Dockerfile
push: true
tags: |
${{ env.IMAGE_PREFIX }}/engine:${{ github.sha }}
${{ env.IMAGE_PREFIX }}/engine:latest
build-db-processor:
needs: changes
if: needs.changes.outputs.db-processor == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push db-processor
uses: docker/build-push-action@v6
with:
context: .
file: docker/db-processor/Dockerfile
push: true
tags: |
${{ env.IMAGE_PREFIX }}/db-processor:${{ github.sha }}
${{ env.IMAGE_PREFIX }}/db-processor:latest
build-websocket:
needs: changes
if: needs.changes.outputs.websocket == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push websocket
uses: docker/build-push-action@v6
with:
context: .
file: docker/websocket/Dockerfile
push: true
tags: |
${{ env.IMAGE_PREFIX }}/websocket:${{ github.sha }}
${{ env.IMAGE_PREFIX }}/websocket:latest
build-market-maker:
needs: changes
if: needs.changes.outputs.market-maker == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push market-maker
uses: docker/build-push-action@v6
with:
context: .
file: docker/market-maker/Dockerfile
push: true
tags: |
${{ env.IMAGE_PREFIX }}/market-maker:${{ github.sha }}
${{ env.IMAGE_PREFIX }}/market-maker:latest
deploy:
needs:
- build-backend
- build-engine
- build-db-processor
- build-websocket
- build-market-maker
if: always() && !failure() && !cancelled()
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Copy compose file to VM
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_KEY }}
passphrase: ${{ secrets.SSH_PASSPHRASE }}
source: docker/compose-files/docker-compose-deploy.yml
target: /home/${{ secrets.SSH_USERNAME }}/exchange/
strip_components: 2
- name: Deploy stack to VM
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_KEY }}
passphrase: ${{ secrets.SSH_PASSPHRASE }}
script: |
set -euo pipefail
cd /home/${{ secrets.SSH_USERNAME }}/exchange
for file in .env .env.backend .env.engine .env.db-processor .env.websocket .env.market-maker; do
if [ ! -f "$file" ]; then
echo "Missing $file in $(pwd). Create it on the VM before deployment."
exit 1
fi
done
export IMAGE_PREFIX="${{ env.IMAGE_PREFIX }}"
export IMAGE_TAG="latest"
docker compose -f docker-compose-deploy.yml pull backend engine db-processor websocket market-maker
docker compose -f docker-compose-deploy.yml up -d redis timescaledb backend engine db-processor websocket market-maker
# Wait for TimescaleDB before letting schema setup run
until docker exec timescaledb pg_isready -U exchange; do sleep 2; done
migrate:
needs: deploy
if: always() && !failure() && !cancelled()
runs-on: ubuntu-latest
steps:
- name: Run migrations and seeds on VM
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_KEY }}
passphrase: ${{ secrets.SSH_PASSPHRASE }}
script: |
set -euo pipefail
# Load nvm if present, install Node 24 + pnpm if missing
export NVM_DIR="$HOME/.nvm"
if [ ! -s "$NVM_DIR/nvm.sh" ]; then
curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
fi
. "$NVM_DIR/nvm.sh"
nvm install 24
nvm use 24
corepack enable
corepack prepare pnpm@9.0.0 --activate
# Clone or update repo on VM
DEPLOY_DIR="$HOME/exchange"
REPO_DIR="$HOME/exchange-src"
if [ ! -d "$REPO_DIR/.git" ]; then
git clone https://github.com/${{ github.repository }}.git "$REPO_DIR"
fi
cd "$REPO_DIR"
git fetch origin
git checkout ${{ github.sha }}
# Install workspace dependencies
pnpm install --frozen-lockfile
set -a
. "$DEPLOY_DIR/.env"
set +a
: "${TIMESCALE_PASSWORD:?Set TIMESCALE_PASSWORD in $DEPLOY_DIR/.env}"
MIGRATION_DATABASE_URL="${MIGRATION_DATABASE_URL:-postgresql://exchange:${TIMESCALE_PASSWORD}@localhost:5433/exchange}"
cat > packages/database/.env <<EOF
DATABASE_URL=$MIGRATION_DATABASE_URL
DIRECT_URL=$MIGRATION_DATABASE_URL
EOF
cat > apps/db-processor/.env <<EOF
DATABASE_URL=$MIGRATION_DATABASE_URL
TIMESCALE_USER=exchange
TIMESCALE_HOST=localhost
TIMESCALE_DATABASE=exchange
TIMESCALE_PASSWORD=$TIMESCALE_PASSWORD
TIMESCALE_PORT=5433
EOF
# Prisma generate + migrate
pnpm --filter @repo/database exec prisma generate
pnpm --filter @repo/database exec prisma migrate deploy
# Seed Postgres markets (uses packages/database/.env)
pnpm --filter @repo/database run seed
# Seed TimescaleDB schema (uses apps/db-processor/.env)
pnpm --filter db-processor run seed
verify:
needs: migrate
if: always() && !failure() && !cancelled()
runs-on: ubuntu-latest
steps:
- name: Verify app health and prune images
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_KEY }}
passphrase: ${{ secrets.SSH_PASSPHRASE }}
script: |
set -euo pipefail
cd /home/${{ secrets.SSH_USERNAME }}/exchange
# App containers may have been crash-looping until migrate finished.
# Restart them once now that schema + seeds are in place.
docker compose -f docker-compose-deploy.yml up -d --no-deps backend engine db-processor websocket market-maker
sleep 10
curl -f --retry 5 --retry-delay 3 http://localhost:3001/health || exit 1
docker image prune -f