Skip to main content

GitHub Workflows Deep Dive

The Ever Works platform uses 11 GitHub Actions workflow files in .github/workflows/ to automate CI, Docker image builds, Kubernetes deployments, CLI publishing, and Trigger.dev deployments across three environments (dev, stage, prod).

Workflow Inventory

Workflow FileNameTriggerPurpose
ci.ymlCIPush/PR to main, develop, stageLint, build, test
docker-build-publish-dev.ymlBuild and Publish Docker Images DevPush to developBuild Docker images for dev
docker-build-publish-stage.ymlBuild and Publish Docker Images StagePush to stageBuild Docker images for stage
docker-build-publish-prod.ymlBuild and Publish Docker Images ProdPush to mainBuild Docker images for prod
deploy-do-dev.ymlDeploy to DO DevAfter Docker Dev completesDeploy to K8s dev
deploy-do-stage.ymlDeploy to DO StageAfter Docker Stage completesDeploy to K8s stage
deploy-do-prod.ymlDeploy to DO ProdAfter Docker Prod completesDeploy to K8s prod
release-trigger-dev.ymlDeploy to Trigger.dev DevAfter CI on developDeploy Trigger.dev dev
release-trigger-stage.ymlDeploy to Trigger.dev StageAfter CI on stageDeploy Trigger.dev staging
release-trigger-prod.ymlDeploy to Trigger.dev ProdAfter CI on mainDeploy Trigger.dev prod
publish-cli.ymlBuild and Publish CLIsPush to main, tags, manualPublish CLI packages

Pipeline Flow

CI Workflow (ci.yml)

The foundational workflow that all other workflows depend on.

Triggers:

  • Push to main, develop, stage
  • Pull requests targeting those branches
  • Manual workflow_dispatch

Concurrency: Groups by workflow + ref with cancel-in-progress: true to avoid redundant runs.

Runner: ubicloud-standard-8 (high-performance runner)

Steps:

StepCommandPurpose
Checkoutactions/checkout@v4Clone repository
Install pnpmpnpm/action-setup@v3 (v10.13.1)Package manager setup
Setup Node.jsactions/setup-node@v4 (20.x)Node.js with pnpm cache
Install depspnpm install --frozen-lockfileReproducible install
Format checkpnpm format:checkPrettier validation
Build allpnpm buildTurborepo build
Run testspnpm testAll test suites
Build Internal CLIpnpm build:cli (apps/internal-cli)CLI compilation
Build External CLIpnpm build:cli (apps/cli)CLI compilation
Test Internal CLIpnpm test:cli (apps/internal-cli)CLI tests
Test External CLIpnpm test:cli (apps/cli)CLI tests

Secrets Used: API_URL, WEB_URL

Docker Build Workflows

Three identical workflows for dev/stage/prod that build and push Docker images to multiple registries.

Build Process (per environment)

Two parallel jobs build the API and Web images:

Job: ever-works-api

- Build with Docker Buildx
- File: .deploy/docker/api/Dockerfile
- Platform: linux/amd64
- Tags: ghcr.io/ever-works/ever-works-api-{env}:latest
everco/ever-works-api-{env}:latest
registry.digitalocean.com/ever/ever-works-api-{env}:latest
- Cache: registry-based layer caching
- Build args: NODE_ENV={environment}

Job: ever-works-web (same pattern for web image)

Registry Push Order

Each image is pushed to up to four registries:

RegistryActionFailure Policy
GitHub Container Registrydocker/login-action + pushRequired
Docker Hubdocker/login-action + pushcontinue-on-error: true
DigitalOcean Registrydoctl registry login + pushcontinue-on-error: true
CW Container Registrydocker/login-action (push commented out)continue-on-error: true

Secrets Used: DOCKERHUB_USERNAME, DOCKERHUB_TOKEN, DIGITALOCEAN_ACCESS_TOKEN, GITHUB_TOKEN, CW_DOCKER_REGISTRY, CW_DOCKER_USER, CW_DOCKER_USER_PASSWORD

Kubernetes Deploy Workflows

Three workflows (deploy-do-dev.yml, deploy-do-stage.yml, deploy-do-prod.yml) deploy to DigitalOcean Kubernetes.

Trigger

Each runs after its corresponding Docker build workflow completes:

on:
workflow_run:
workflows: ['Build and Publish Docker Images {Env}']
branches: [{ branch }]
types: [completed]

Deployment Steps

StepDescription
Install doctlDigitalOcean CLI setup
Save kubeconfigShort-lived credentials (600s expiry)
Write DB certificateDecode base64 CA certificate
Generate TLS secretsCreate API and Web TLS secrets for ingress
Apply K8s manifestsenvsubst + kubectl apply
Restart podsRolling restart to pick up :latest images

Environment Variables (injected via envsubst)

The manifests receive a comprehensive set of environment variables:

Application:

VariableExample
WEB_URLhttps://app.ever.works (prod) / https://appdev.ever.works (dev)
ALLOWED_ORIGINShttps://app.ever.works,https://api.ever.works
JWT_SECRETFrom secrets
AUTH_SECRETFrom secrets

Trigger.dev:

VariableDescription
TRIGGER_ENABLEDEnable/disable Trigger.dev
TRIGGER_SECRET_KEYTrigger.dev authentication
TRIGGER_INTERNAL_SECRETInternal API secret

OAuth:

VariableDescription
GH_CLIENT_ID / GH_CLIENT_SECRETGitHub OAuth
GH_CALLBACK_URLGitHub OAuth callback
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRETGoogle OAuth
GOOGLE_CALLBACK_URLGoogle OAuth callback

Plugins:

VariableDescription
PLUGIN_OPENROUTER_API_KEYOpenRouter AI provider
PLUGIN_OPENROUTER_DEFAULT_MODELDefault model
PLUGIN_OPENROUTER_SIMPLE_MODELSimple tasks model
PLUGIN_OPENROUTER_MEDIUM_MODELMedium tasks model
PLUGIN_OPENROUTER_COMPLEX_MODELComplex tasks model
PLUGIN_GITHUB_CLIENT_ID / CLIENT_SECRETGitHub plugin OAuth
PLUGIN_TAVILY_API_KEYTavily search
PLUGIN_SCREENSHOTONE_ACCESS_KEY / SECRET_KEYScreenshot provider

Database:

VariableDescription
DATABASE_TYPEDatabase driver
DATABASE_URLConnection string
DATABASE_HOST / PORT / USERNAME / PASSWORD / NAMEIndividual connection params
DATABASE_SSL_MODEEnable SSL/TLS
DATABASE_CA_CERTCA certificate (base64)

Mail:

VariableDescription
MAILER_PROVIDERMail transport (smtp or resend)
EMAIL_FROMSender address
SMTP_HOST / PORT / SECURE / USER / PASSWORDSMTP config
RESEND_APIKEY / RESEND_EMAIL_FROMResend config

Environment URLs

EnvironmentWeb URLAPI URL
devhttps://appdev.ever.workshttps://apidev.ever.works
stage(configured in stage manifest)(configured in stage manifest)
prodhttps://app.ever.workshttps://api.ever.works

Trigger.dev Deploy Workflows

Three workflows deploy background tasks to Trigger.dev across environments.

Trigger

Each runs after CI completes on the corresponding branch:

on:
workflow_run:
workflows: ['CI']
branches: [{ branch }]
types: [completed]

Runner

All use ubicloud-standard-2 (lighter runner, as deployment is simpler).

Steps

StepDevStage/Prod
Build packagespnpm build --filter './packages/**'Same
Prepare plugins--pnpm prepare:plugins
DeployLogin only (npx [email protected] login)npx [email protected] deploy --env {env}

The dev workflow only verifies Trigger.dev connectivity (login) without deploying, because the dev environment uses NestJS built-in scheduling instead.

Secrets Used: TRIGGER_ACCESS_TOKEN

CLI Publish Workflow (publish-cli.yml)

The most complex workflow, handling builds, version bumps, publishing, and GitHub releases.

Triggers

  • Push to main branch
  • Tags: v*, cli-v*, internal-cli-v*
  • Manual dispatch with options:
InputTypeDescription
publish_internal_clibooleanPublish internal CLI
publish_external_clibooleanPublish external CLI
do_version_bumpbooleanBump version before publish
version_bumpchoicepatch, minor, major

Jobs

Build Job: Builds all packages, then builds and tests both CLIs. Uploads dist/ as artifacts.

Publish Jobs: Download artifacts, optionally bump version, publish to npm:

  • Internal CLI: npm publish --access restricted (private)
  • External CLI: npm publish --access public (public)

Release Jobs: Create GitHub releases with installation instructions using softprops/action-gh-release@v2.

Tag PatternRelease TypePackages
v*CombinedBoth CLIs
cli-v*External onlyever-works-cli
internal-cli-v*Internal only@ever-works/cli

Secrets Used: NPM_TOKEN, API_URL, WEB_URL

Complete Secrets Reference

SecretUsed In
API_URLCI, CLI publish
WEB_URLCI, CLI publish
JWT_SECRETK8s deploys
AUTH_SECRETK8s deploys
DIGITALOCEAN_ACCESS_TOKENDocker builds, K8s deploys
DOCKERHUB_USERNAME / DOCKERHUB_TOKENDocker builds
GITHUB_TOKENDocker builds (auto-provided)
NPM_TOKENCLI publish
TRIGGER_ACCESS_TOKENTrigger.dev deploys
TRIGGER_ENABLED / SECRET_KEY / INTERNAL_SECRETK8s deploys
DATABASE_* (7 vars)K8s deploys
SMTP_* (6 vars)K8s deploys
RESEND_* (2 vars)K8s deploys
PLUGIN_* (10 vars)K8s deploys
GH_* / GOOGLE_* (6 vars)K8s deploys
INGRESS_* (4 vars)K8s deploys
CW_DOCKER_* (3 vars)Docker builds

Source Files

FilePurpose
.github/workflows/ci.ymlCI pipeline
.github/workflows/docker-build-publish-dev.ymlDocker build (dev)
.github/workflows/docker-build-publish-stage.ymlDocker build (stage)
.github/workflows/docker-build-publish-prod.ymlDocker build (prod)
.github/workflows/deploy-do-dev.ymlK8s deploy (dev)
.github/workflows/deploy-do-stage.ymlK8s deploy (stage)
.github/workflows/deploy-do-prod.ymlK8s deploy (prod)
.github/workflows/release-trigger-dev.ymlTrigger.dev (dev)
.github/workflows/release-trigger-stage.ymlTrigger.dev (stage)
.github/workflows/release-trigger-prod.ymlTrigger.dev (prod)
.github/workflows/publish-cli.ymlCLI build and publish