Skip to content

feat(root): new deployment and rollback action #8126

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 43 commits into from
Apr 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
fd4d0c3
feat(ci): add GitHub Actions workflow for deploying to Novu Cloud
merrcury Apr 1, 2025
013da7d
feat(ci): set default deployment environment to development
merrcury Apr 1, 2025
f123579
Merge branch 'next' of github.com:novuhq/novu into feat/actions
merrcury Apr 1, 2025
bb6cba5
fix(deploy): standardize environment naming to 'Development'
merrcury Apr 1, 2025
53b8b33
fix(deploy): update environment variable extraction in workflow
merrcury Apr 1, 2025
09ab87a
fix(deploy): streamline AWS region configuration and remove redundant…
merrcury Apr 1, 2025
30558ec
fix(api): update Dockerfile to use base image from GitHub Container R…
merrcury Apr 1, 2025
09772ef
fix(api): update Dockerfile to use specific base image from GitHub Co…
merrcury Apr 1, 2025
7312658
fix(deploy): update repository path in workflow to use secret for ECR…
merrcury Apr 1, 2025
f6ed3b5
fix(deploy): update workflow descriptions for clarity and consistency
merrcury Apr 2, 2025
4cd2a77
fix(worker): update Dockerfile to use specific base image for develop…
merrcury Apr 2, 2025
5c67d85
fix(deploy): simplify Docker build command in deployment workflow
merrcury Apr 2, 2025
8e3b1fa
fix(ws): remove unnecessary package copies from Dockerfile
merrcury Apr 2, 2025
a945909
fix(ws): add framework package copy to Dockerfile
merrcury Apr 2, 2025
a94073a
fix(ws): update Dockerfile to use a new base image and streamline setup
merrcury Apr 2, 2025
0f6de8d
fix(deploy): update default environment to staging and adjust environ…
merrcury Apr 7, 2025
e4cc2c7
fix(deploy): enhance deployment matrix generation and update service …
merrcury Apr 7, 2025
f0245f2
fix(deploy): update WORKER_SERVICE reference from secrets to vars in …
merrcury Apr 7, 2025
942348e
fix(deploy): clean up whitespace and enhance environment variable han…
merrcury Apr 7, 2025
1791af6
fix(deploy): update deploy matrix to correctly handle worker service …
merrcury Apr 7, 2025
f3c7f6a
fix(deploy): update environment variables for production deployment a…
merrcury Apr 8, 2025
1f6fce9
fix(deploy): correct key name in deploy matrix for worker service
merrcury Apr 8, 2025
7174049
fix(deploy): enhance deploy matrix to include image details and updat…
merrcury Apr 8, 2025
7190bf1
Merge branch 'next' of github.com:novuhq/novu into feat/actions
merrcury Apr 8, 2025
95c4551
fix(docker): update base image for dev and prod stages to use dev_base
merrcury Apr 8, 2025
ea051f9
feat(deploy): add New Relic and Sentry release steps to deployment wo…
merrcury Apr 14, 2025
b7d6daf
fix(deploy): update environment matrix to use full environment array
merrcury Apr 14, 2025
6f2d0d3
fix(deploy): correct environment variable assignment and add sync sta…
merrcury Apr 14, 2025
5f0c465
fix(deploy): add condition to new_relic_release job to check for non-…
merrcury Apr 14, 2025
4f041fa
fix(docker): remove unnecessary COPY command for packages/client in D…
merrcury Apr 14, 2025
dbfcda4
fix(deploy): add run-name to deployment workflow for better clarity
merrcury Apr 14, 2025
0e5b544
fix(deploy): improve run-name formatting for better readability
merrcury Apr 14, 2025
7d41ada
fix(deploy): improve run-name formatting for better clarity
merrcury Apr 14, 2025
b3e4676
fix(deploy): update run-name for clarity and add condition to build job
merrcury Apr 14, 2025
dad2fdc
fix(deploy): add check for empty service matrix to prevent deployment…
merrcury Apr 14, 2025
0f1717e
fix(deploy): add validation to ensure at least one service is selecte…
merrcury Apr 14, 2025
16ad67d
fix(rollback): enhance rollback workflow to validate selected service…
merrcury Apr 14, 2025
f0e2b65
fix(rollback): correct syntax in AWS CLI command for updating service…
merrcury Apr 14, 2025
cd8ad97
fix(rollback): remove trailing comma from webhook in run-name and upd…
merrcury Apr 14, 2025
9bf8515
fix(rollback): update environment description and add rollback signof…
merrcury Apr 14, 2025
e4b620b
fix(rollback): update rollback signoff description and options for cl…
merrcury Apr 14, 2025
a704252
fix(deploy): remove unnecessary outputs and echo command from build job
merrcury Apr 17, 2025
e1fd01c
Merge branch 'next' of github.com:novuhq/novu into feat/actions
merrcury Apr 22, 2025
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
245 changes: 202 additions & 43 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -1,35 +1,51 @@
name: Deploy to Novu Cloud
run-name: >
Deploying to
${{
github.event.inputs.deploy_api == 'true' && 'api, ' || ''
}}${{
github.event.inputs.deploy_worker == 'true' && 'worker, ' || ''
}}${{
github.event.inputs.deploy_ws == 'true' && 'ws, ' || ''
}}${{
github.event.inputs.deploy_webhook == 'true' && 'webhook ' || ''
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Webhook is not part of Novu V2, so I am not sure if we need it as an option.

}}on ${{ github.event.inputs.environment }}
description: |
This workflow deploys the Novu Cloud application to different environments and services based on the selected options.
It builds Docker images, pushes them to Amazon ECR, and deploys them to Amazon ECS.
Additionally, it creates Sentry releases and New Relic deployment markers.

on:
workflow_dispatch:
inputs:
environment:
description: 'Environment to deploy to'
required: true
type: choice
default: development
default: staging
options:
- development
- staging
- production-us
- production-eu
- production-both

deploy_api:
description: 'Deploy API?'
description: 'Deploy API'
required: true
type: boolean
default: true
deploy_worker:
description: 'Deploy Worker?'
description: 'Deploy Worker'
required: true
type: boolean
default: false
deploy_ws:
description: 'Deploy WS?'
description: 'Deploy WS'
required: true
type: boolean
default: false
deploy_webhook:
description: 'Deploy Webhook?'
description: 'Deploy Webhook'
required: true
type: boolean
default: false
Expand All @@ -40,34 +56,52 @@ jobs:
outputs:
env_matrix: ${{ steps.set-matrix.outputs.env_matrix }}
service_matrix: ${{ steps.set-matrix.outputs.service_matrix }}
deploy_matrix: ${{ steps.set-matrix.outputs.deploy_matrix }}
nr_matrix: ${{ steps.set-matrix.outputs.nr_matrix }}
steps:
- name: Generate Environment & Service Matrices
- name: Validate Selected Services
run: |
if [ "${{ github.event.inputs.deploy_api }}" != "true" ] && \
[ "${{ github.event.inputs.deploy_worker }}" != "true" ] && \
[ "${{ github.event.inputs.deploy_ws }}" != "true" ] && \
[ "${{ github.event.inputs.deploy_webhook }}" != "true" ]; then
echo "Error: At least one service must be selected for deployment."
exit 1
fi

- name: Generate Environment, Service, and Deploy Matrices
id: set-matrix
env:
WORKER_SERVICE: ${{ vars.WORKER_SERVICE }}
run: |
envs=()
services=()
deploy_matrix=()
nr=()

# Collect selected environments
if [ "${{ github.event.inputs.environment }}" == "development" ]; then
envs+=("\"development\"")
if [ "${{ github.event.inputs.environment }}" == "staging" ]; then
envs+=("\"staging-eu\"")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have staging-eu?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Staging exist in EU region. Some environments name were taken up here in actions. So I made parity in nomenclature with env-location.

fi
if [ "${{ github.event.inputs.environment }}" == "production-us" ]; then
envs+=("\"production-us\"")
envs+=("\"prod-us\"")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit picky comment: I'd prefer production-us it's more descriptive ;)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is already a env with that name

fi
if [ "${{ github.event.inputs.environment }}" == "production-eu" ]; then
envs+=("\"production-eu\"")
envs+=("\"prod-eu\"")
fi
if [ "${{ github.event.inputs.environment }}" == "production-both" ]; then
envs+=("\"production-us\"")
envs+=("\"production-eu\"")
envs+=("\"prod-us\"")
envs+=("\"prod-eu\"")
fi

# Collect selected services
if [ "${{ github.event.inputs.deploy_api }}" == "true" ]; then
services+=("\"api\"")
nr+=("\"api\"")
fi
if [ "${{ github.event.inputs.deploy_worker }}" == "true" ]; then
services+=("\"worker\"")
nr+=("\"worker\"")
fi
if [ "${{ github.event.inputs.deploy_ws }}" == "true" ]; then
services+=("\"ws\"")
Expand All @@ -76,26 +110,68 @@ jobs:
services+=("\"webhook\"")
fi

# Parse service secrets and generate deploy_matrix
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where will we see those matrices? 👏

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its for action to use 😅

for service in "${services[@]}"; do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am curious if there is a better way to construct this JSON using an inline node script or maybe zx from Google. This bash string interpolation is easy to understand but it's also super weird.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I'll improve in later PR as it not pressing enough.

if [ "$service" == "\"worker\"" ]; then
IFS=',' read -r -a worker_services <<< "$WORKER_SERVICE"
for worker_service in $(echo "$WORKER_SERVICE" | jq -c '.[]'); do
cluster_name=$(echo "$worker_service" | jq -r '.cluster_name')
container_name=$(echo "$worker_service" | jq -r '.container_name')
service_name=$(echo "$worker_service" | jq -r '.service')
task_name=$(echo "$worker_service" | jq -r '.task_name')
image=$(echo "$worker_service" | jq -r '.image')
deploy_matrix+=("{\"cluster_name\": \"$cluster_name\", \"container_name\": \"$container_name\", \"service_name\": \"$service_name\", \"task_name\": \"$task_name\", \"image\": \"$image\"}")
done
elif [ "$service" == "\"api\"" ]; then
cluster_name=api-cluster
container_name=api-container
service_name=api-service
task_name=api-task
image=api
deploy_matrix+=("{\"cluster_name\": \"$cluster_name\", \"container_name\": \"$container_name\", \"service_name\": \"$service_name\", \"task_name\": \"$task_name\", \"image\": \"$image\"}")
elif [ "$service" == "\"ws\"" ]; then
cluster_name=ws-cluster
container_name=ws-container
service_name=ws-service
task_name=ws-task
image=ws
deploy_matrix+=("{\"cluster_name\": \"$cluster_name\", \"container_name\": \"$container_name\", \"service_name\": \"$service_name\", \"task_name\": \"$task_name\", \"image\": \"$image\"}")
elif [ "$service" == "\"webhook\"" ]; then
cluster_name=webhook-cluster
container_name=webhook-container
service_name=webhook-service
task_name=webhook-task
image=webhook
deploy_matrix+=("{\"cluster_name\": \"$cluster_name\", \"container_name\": \"$container_name\", \"service_name\": \"$service_name\", \"task_name\": \"$task_name\", \"image\": \"$image\"}")
fi
done

env_matrix="{\"environment\": [$(
IFS=','; echo "${envs[*]}"
)]}"
service_matrix="{\"service\": [$(
IFS=','; echo "${services[*]}"
)]}"

deploy_matrix="[$(
IFS=','; echo "${deploy_matrix[*]}"
)]"
nr_matrix="[$(
IFS=','; echo "${nr[*]}"
)]"
echo "env_matrix=$env_matrix" >> $GITHUB_OUTPUT
echo "service_matrix=$service_matrix" >> $GITHUB_OUTPUT
echo "deploy_matrix=$deploy_matrix" >> $GITHUB_OUTPUT
echo "nr_matrix=$nr_matrix" >> $GITHUB_OUTPUT

build:
needs: prepare-matrix
timeout-minutes: 60
runs-on: ubuntu-latest
outputs:
docker_image: ${{ steps.build-image.outputs.IMAGE }}
environment: ${{ fromJson(needs.prepare-matrix.outputs.env_matrix).environment[0] }}
strategy:
matrix:
service: ${{ fromJson(needs.prepare-matrix.outputs.service_matrix).service }}

steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down Expand Up @@ -124,24 +200,17 @@ jobs:
uses: docker/setup-buildx-action@v3
with:
driver-opts: 'image=moby/buildkit:v0.13.1'

- name: Prepare Variables
run: |
set -e
if [[ "$(echo ${{ fromJson(needs.prepare-matrix.outputs.env_matrix).environment }})" == "development" ]]; then
echo "AWS_REGION=eu-west-2" >> $GITHUB_ENV
else
echo "AWS_REGION=us-east-1" >> $GITHUB_ENV
fi
echo "BULL_MQ_PRO_NPM_TOKEN=${{ secrets.BULL_MQ_PRO_NPM_TOKEN }}" >> $GITHUB_ENV
run: echo "BULL_MQ_PRO_NPM_TOKEN=${{ secrets.BULL_MQ_PRO_NPM_TOKEN }}" >> $GITHUB_ENV

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID}}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
aws-region: ${{ vars.AWS_REGION }}

- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
Expand All @@ -150,33 +219,123 @@ jobs:
id: build-image
env:
REGISTRY: ${{ steps.login-ecr.outputs.registry }}
REPOSITORY: novu/${{ matrix.service }}
REPOSITORY: ${{ vars.ECR_PREFIX }}
SERVICE: ${{ matrix.service }}
IMAGE_TAG: ${{ github.sha }}
DOCKER_BUILD_ARGUMENTS: >
--platform=linux/amd64
--output=type=image,name=$REGISTRY/$REPOSITORY,push-by-digest=true,name-canonical=true
--output=type=image,name=$REGISTRY/$REPOSITORY/$SERVICE,push-by-digest=true,name-canonical=true
run: |
cp scripts/dotenvcreate.mjs apps/$SERVICE/src/dotenvcreate.mjs
cd apps/$SERVICE && pnpm --silent --workspace-root pnpm-context -- apps/$SERVICE/Dockerfile | BULL_MQ_PRO_NPM_TOKEN=${BULL_MQ_PRO_NPM_TOKEN} docker buildx build --secret id=BULL_MQ_PRO_NPM_TOKEN --build-arg PACKAGE_PATH=apps/$SERVICE - -t novu-$SERVICE --load $DOCKER_BUILD_ARGUMENTS
docker tag novu-$SERVICE $REGISTRY/$REPOSITORY:latest
docker tag novu-$SERVICE $REGISTRY/$REPOSITORY:prod
docker tag novu-$SERVICE $REGISTRY/$REPOSITORY:$IMAGE_TAG

docker push $REGISTRY/$REPOSITORY:prod
docker push $REGISTRY/$REPOSITORY:latest
docker push $REGISTRY/$REPOSITORY:$IMAGE_TAG
echo "IMAGE=$REGISTRY/$REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
cd apps/$SERVICE && pnpm run docker:build
docker tag novu-$SERVICE $REGISTRY/$REPOSITORY/$SERVICE:latest
docker tag novu-$SERVICE $REGISTRY/$REPOSITORY/$SERVICE:$IMAGE_TAG
docker push $REGISTRY/$REPOSITORY/$SERVICE:latest
docker push $REGISTRY/$REPOSITORY/$SERVICE:$IMAGE_TAG

deploy:
needs: [build, prepare-matrix]
runs-on: ubuntu-latest
strategy:
matrix:
env: ${{ fromJson(needs.prepare-matrix.outputs.env_matrix).environment }}
service: ${{ fromJson(needs.prepare-matrix.outputs.service_matrix).service }}
service: ${{ fromJson(needs.prepare-matrix.outputs.deploy_matrix) }}

environment: ${{ matrix.env }}

steps:
- name: Print Important Info
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}

- name: Download task definition
env:
ECS_PREFIX: ${{ vars.ECS_PREFIX }}
TASK_NAME: ${{ matrix.service.task_name }}
run: |
echo "Deploying ${{ matrix.service }} to ${{ matrix.env }}"
echo "Docker Image: ${{ needs.build.outputs.docker_image }}"
aws ecs describe-task-definition --task-definition ${ECS_PREFIX}-${TASK_NAME} \
--query taskDefinition > task-definition.json

- name: Render Amazon ECS task definition
id: render-web-container
uses: aws-actions/amazon-ecs-render-task-definition@39c13cf530718ffeb524ec8ee0c15882bcb13842
with:
task-definition: task-definition.json
container-name: ${{ vars.ECS_PREFIX }}-${{ matrix.service.container_name }}
image: ${{secrets.ECR_URI}}/${{ vars.ECR_PREFIX }}/${{ matrix.service.image }}:${{ github.sha }}

- name: Deploy to Amazon ECS service
uses: aws-actions/amazon-ecs-deploy-task-definition@3e7310352de91b71a906e60c22af629577546002
with:
task-definition: ${{ steps.render-web-container.outputs.task-definition }}
service: ${{ vars.ECS_PREFIX }}-${{ matrix.service.service_name }}
cluster: ${{ vars.ECS_PREFIX }}-${{ matrix.service.cluster_name }}
wait-for-service-stability: true


sentry_release:
needs: [deploy, prepare-matrix]
runs-on: ubuntu-latest
strategy:
matrix:
service: ${{ fromJson(needs.prepare-matrix.outputs.service_matrix).service }}
environment: ${{ fromJson(needs.prepare-matrix.outputs.env_matrix).environment[0] }}

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Get NPM Version
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't use NPM semver for the services. We use it only for NPM packages. That is, I suggest using the latest commit hash to pinpoint the latest deployed code.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have another PR for this

id: package-version
uses: martinbeentjes/npm-get-version-action@main
with:
path: apps/${{ matrix.service }}

- name: Create Sentry release
uses: getsentry/action-release@v1
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
SENTRY_PROJECT: ${{ matrix.service }}
with:
version: ${{ steps.package-version.outputs.current-version}}
version_prefix: v
environment: ${{vars.SENTRY_ENV}}
ignore_empty: true
ignore_missing: true

new_relic_release:
needs: [deploy, prepare-matrix]
if: ${{ fromJson(needs.prepare-matrix.outputs.nr_matrix) != '[]' }}
runs-on: ubuntu-latest
strategy:
matrix:
env: ${{ fromJson(needs.prepare-matrix.outputs.env_matrix).environment }}
nr: ${{ fromJson(needs.prepare-matrix.outputs.nr_matrix) }}
environment: ${{ matrix.env }}

steps:
- name: New Relic Application Deployment Marker
uses: newrelic/[email protected]
with:
region: EU
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
guid: ${{ matrix.nr == 'api' && secrets.NEW_RELIC_API_GUID || matrix.nr == 'worker' && secrets.NEW_RELIC_Worker_GUID }}
version: '${{ github.sha }}'
user: '${{ github.actor }}'
description: 'Novu Cloud Deployment'

sync_novu_state:
needs: [deploy, prepare-matrix]
runs-on: ubuntu-latest
if: github.event.inputs.deploy_api == 'true'
environment: ${{ fromJson(needs.prepare-matrix.outputs.env_matrix).environment[0] }}
steps:
- name: Sync State to Novu
uses: novuhq/actions-novu-sync@v2
with:
secret-key: ${{ secrets.NOVU_INTERNAL_SECRET_KEY }}
bridge-url: ${{ vars.NOVU_BRIDGE_URL }}
Loading
Loading