CI/CD with GitHub Actions
Prerequisites:
- GitHub repository
- AWS account with OIDC configured
- Jaypie CDK package (for deployments)
Overview
Section titled âOverviewâJaypie projects use GitHub Actions for CI/CD with environment-specific deployments. The standard workflow includes:
- Check (lint, type, test) on feature branches
- Deploy on main branch and release tags
- Personal sandbox builds for developer testing
Workspace Naming Conventions
Section titled âWorkspace Naming Conventionsâ| Directory | Purpose |
|---|---|
packages/ |
Default workspace for npm packages (preferred when only one namespace needed) |
workspaces/ |
CDK-deployed infrastructure, sites, and other non-npm work |
Environments
Section titled âEnvironmentsâJaypie assumes multiple deployed environments:
| Environment | Purpose |
|---|---|
| production | Live environment, standalone |
| development | Validates multi-environment deployment works |
| sandbox | Shared testing environment |
| personal | Developer-specific builds tied to GitHub actor |
Workflow Naming Conventions
Section titled âWorkflow Naming ConventionsâWorkflow File Naming
Section titled âWorkflow File Namingâ| Pattern | Purpose | Example |
|---|---|---|
deploy-env-$ENV.yml |
Deploy all stacks to an environment | deploy-env-sandbox.yml |
deploy-$ENV.yml |
Legacy alias for environment deploy | deploy-sandbox.yml |
deploy-stack-$STACK.yml |
Deploy specific stack content | deploy-stack-documentation.yml |
Tag Naming
Section titled âTag Namingâ| Pattern | Purpose | Example |
|---|---|---|
v* |
Production version release | v1.2.3 |
$ENV-* |
Deploy to specific environment | sandbox-hotfix |
stack-$STACK-* |
Deploy specific stack | stack-documentation-v1.0.0 |
Always prefer full words for maximum clarity.
Triggers
Section titled âTriggersâ| Environment | Trigger |
|---|---|
| production | tags: v*, production-* |
| development | branches: [main, development/*], tags: development-* |
| sandbox | branches: [feat/*, sandbox/*], tags: sandbox-* |
| personal | branches-ignore: [main, develop, nobuild-*, nobuild/*, sandbox, sandbox-*, sandbox/*] |
Stack Triggers
Section titled âStack Triggersâ| Stack | Trigger |
|---|---|
| documentation | Push to main with path filter, tags: stack-documentation-* |
Dependencies
Section titled âDependenciesâ| Environment | Lint/Test Dependency |
|---|---|
| production | Required to pass before deploy |
| development | Required to pass before deploy |
| sandbox | Runs in parallel with deploy |
| personal | Runs in parallel with deploy |
Check Workflow
Section titled âCheck Workflowâ.github/workflows/npm-check.yml:
name: Check
on: push: branches: - 'feat/*' - 'fix/*' - 'devin/*'
jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version: '22' cache: 'npm' - run: npm ci - run: npm run lint
typecheck: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version: '22' cache: 'npm' - run: npm ci - run: npm run typecheck
test: runs-on: ubuntu-latest strategy: matrix: node-version: ['22', '24', '25'] steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} cache: 'npm' - run: npm ci - run: npm testDeploy Workflow
Section titled âDeploy WorkflowâEnvironment Deployment
Section titled âEnvironment Deploymentâ.github/workflows/deploy-env-sandbox.yml:
name: Deploy to Sandbox
on: push: branches: - 'feat/*' - 'sandbox/*' tags: - 'sandbox-*'
concurrency: group: deploy-env-sandbox
permissions: id-token: write contents: read
env: AWS_REGION: us-east-1 CDK_PATH: packages/cdk
jobs: lint-and-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: ./.github/actions/setup-node-and-cache - uses: ./.github/actions/npm-install-build - run: npm run lint - run: npm run test
deploy: runs-on: ubuntu-latest environment: sandbox steps: - uses: actions/checkout@v6
- uses: ./.github/actions/setup-environment with: aws-region: ${{ vars.AWS_REGION }} aws-role-arn: ${{ vars.AWS_ROLE_ARN }} project-env: ${{ vars.PROJECT_ENV }}
- uses: ./.github/actions/configure-aws with: aws-role-arn: ${{ vars.AWS_ROLE_ARN }} aws-region: ${{ vars.AWS_REGION }}
- uses: ./.github/actions/setup-node-and-cache - uses: ./.github/actions/npm-install-build
- uses: ./.github/actions/cdk-deploy with: stack-name: AppStackProduction Deployment
Section titled âProduction Deploymentâ.github/workflows/deploy-env-production.yml:
name: Deploy to Production
on: push: tags: - 'v*' - 'production-*'
concurrency: group: deploy-env-production
permissions: id-token: write contents: read
jobs: lint-and-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: ./.github/actions/setup-node-and-cache - uses: ./.github/actions/npm-install-build - run: npm run lint - run: npm run test
deploy: runs-on: ubuntu-latest needs: lint-and-test environment: production steps: - uses: actions/checkout@v6
- uses: ./.github/actions/setup-environment with: aws-role-arn: ${{ vars.AWS_ROLE_ARN }} project-env: ${{ vars.PROJECT_ENV }}
- uses: ./.github/actions/configure-aws with: aws-role-arn: ${{ vars.AWS_ROLE_ARN }}
- uses: ./.github/actions/setup-node-and-cache - uses: ./.github/actions/npm-install-build
- uses: ./.github/actions/cdk-deploy with: stack-name: AppStackConcurrency
Section titled âConcurrencyâUse concurrency groups without cancel-in-progress to avoid stuck deployments. Environments run in parallel between each other but concurrently within an environment.
Environment Builds
Section titled âEnvironment Buildsâconcurrency: group: deploy-env-productionStack Builds
Section titled âStack Buildsâconcurrency: group: deploy-stack-documentation-${{ github.event.inputs.environment || 'development' }}Personal Builds
Section titled âPersonal Buildsâconcurrency: group: deploy-personal-build-${{ github.actor }}Composite Actions
Section titled âComposite ActionsâAvailable Actions
Section titled âAvailable Actionsâ| Action | Description |
|---|---|
configure-aws |
OIDC authentication with AWS |
setup-node-and-cache |
Node.js setup with dependency caching |
npm-install-build |
Install dependencies and build |
setup-environment |
Configure environment variables |
cdk-deploy |
Build and deploy CDK stack |
configure-aws
Section titled âconfigure-awsâ.github/actions/configure-aws/action.yml:
name: Configure AWS Credentialsdescription: Configure AWS credentials using OIDC
inputs: aws-role-arn: description: AWS IAM role ARN required: true aws-region: description: AWS region required: false default: us-east-1
runs: using: composite steps: - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v6 with: role-to-assume: ${{ inputs.aws-role-arn }} role-session-name: DeployRoleForGitHubSession aws-region: ${{ inputs.aws-region }}setup-environment
Section titled âsetup-environmentâ.github/actions/setup-environment/action.yml:
Configures environment variables with sensible defaults following variable scoping conventions.
Variable Scoping (GitHub Settings):
- Organization: AWS_REGION, LOG_LEVEL, MODULE_LOG_LEVEL, PROJECT_SPONSOR
- Repository: AWS_HOSTED_ZONE, PROJECT_KEY, PROJECT_SERVICE
- Environment: AWS_ROLE_ARN, DATADOG_API_KEY_ARN, PROJECT_ENV, PROJECT_NONCE
cdk-deploy
Section titled âcdk-deployâ.github/actions/cdk-deploy/action.yml:
name: CDK Deploydescription: Build and deploy a CDK stack
inputs: stack-name: description: Name of the CDK stack to deploy required: true cdk-path: description: Path to CDK package required: false default: packages/cdk
runs: using: composite steps: - name: CDK Deploy shell: bash run: npx cdk deploy ${{ inputs.stack-name }} --require-approval never working-directory: ${{ inputs.cdk-path }}GitHub Variable Scoping
Section titled âGitHub Variable ScopingâVariables are configured at different levels in GitHub Settings:
Organization Level
Section titled âOrganization Levelâ| Variable | Description | Default |
|---|---|---|
AWS_REGION |
AWS region | us-east-1 |
LOG_LEVEL |
Application log level | debug |
MODULE_LOG_LEVEL |
Module log level | warn |
PROJECT_SPONSOR |
Organization name | finlaysonstudio |
Repository Level
Section titled âRepository Levelâ| Variable | Description | Default |
|---|---|---|
AWS_HOSTED_ZONE |
Route53 hosted zone | example.com |
PROJECT_KEY |
Project identifier | myapp |
PROJECT_SERVICE |
Service name | stacks |
Environment Level
Section titled âEnvironment Levelâ| Variable | Description | Required |
|---|---|---|
AWS_ROLE_ARN |
IAM role ARN for OIDC | Yes |
DATADOG_API_KEY_ARN |
Datadog API key ARN in Secrets Manager | No |
PROJECT_ENV |
Environment identifier | Yes |
PROJECT_NONCE |
Unique resource identifier | No |
Auto-Generated Variables
Section titled âAuto-Generated VariablesâThese variables are set automatically from GitHub context:
| Variable | Source | Description |
|---|---|---|
CDK_DEFAULT_ACCOUNT |
${{ github.repository_owner }} |
Repository owner |
CDK_DEFAULT_REGION |
AWS_REGION |
Same as AWS region |
CDK_ENV_REPO |
${{ github.repository }} |
Repository name (owner/repo) |
PROJECT_COMMIT |
${{ github.sha }} |
Current commit SHA |
PROJECT_VERSION |
package.json |
Version from package.json |
Environment Variables Reference
Section titled âEnvironment Variables ReferenceâApplication Variables
Section titled âApplication VariablesâApplication variables may be prefixed with APP_. Build systems and frontend frameworks may require their own prefixes (e.g., NUXT_, VITE_).
CDK Variables
Section titled âCDK VariablesâEnvironment-specific variables for CDK use the CDK_ENV_ prefix.
| Variable | Source | Example |
|---|---|---|
CDK_ENV_API_HOSTED_ZONE |
Workflow | Route53 hosted zone for API |
CDK_ENV_API_SUBDOMAIN |
Workflow | API subdomain |
CDK_ENV_REPO |
Workflow | GitHub repository |
CDK_ENV_WEB_HOSTED_ZONE |
Workflow | Route53 hosted zone for web |
CDK_ENV_WEB_SUBDOMAIN |
Workflow | Web subdomain |
CDK_ENV_BUCKET |
Stack | S3 bucket name |
CDK_ENV_QUEUE_URL |
Stack | SQS queue URL |
CDK_ENV_SNS_ROLE_ARN |
Stack | SNS role ARN |
CDK_ENV_SNS_TOPIC_ARN |
Stack | SNS topic ARN |
Jaypie Variables
Section titled âJaypie VariablesâVariables for Jaypie and the application use the PROJECT_ prefix.
| Variable | Description | Example Values |
|---|---|---|
PROJECT_ENV |
Environment identifier | development, production, sandbox, or GitHub actor |
PROJECT_KEY |
Project shortname/slug | myapp |
PROJECT_NONCE |
Eight-character random string | 860b5a0f |
PROJECT_SERVICE |
Service name | myapp |
PROJECT_SPONSOR |
Project sponsor/organization | finlaysonstudio |
Secrets
Section titled âSecretsâBy default, no secrets are required. Dependencies add secrets as needed.
Secret Naming
Section titled âSecret NamingâSecrets follow the same conventions as variables. Use SECRET_ prefix for secrets that will be resolved at runtime:
SECRET_MONGODB_URI- Secret name in AWS Secrets ManagerMONGODB_URI- Direct value (for local development)
Passing Secrets
Section titled âPassing SecretsâSecrets are passed to CDK via JaypieEnvSecret construct and made available at runtime.
Pass secrets only to steps that need them:
- name: Deploy CDK Stack uses: ./.github/actions/cdk-deploy with: stack-name: AppStack env: SECRET_API_KEY: ${{ secrets.API_KEY }}Never expose secrets in logs or workflow outputs.
Adding Dependency Secrets
Section titled âAdding Dependency SecretsâWhen adding dependencies that require secrets, update the workflow accordingly.
Example: Auth0 Integration
Section titled âExample: Auth0 IntegrationâEnvironment Variables:
AUTH0_AUDIENCE- API identifierAUTH0_CLIENT_ID- Application client IDAUTH0_DOMAIN- Auth0 tenant domain
Environment Secrets:
AUTH0_CLIENT_SECRET- Application client secret
Add to workflow:
- name: Deploy CDK Stack uses: ./.github/actions/cdk-deploy with: stack-name: AppStack env: AUTH0_CLIENT_SECRET: ${{ secrets.AUTH0_CLIENT_SECRET }}Personal Builds
Section titled âPersonal BuildsâPersonal builds allow developers to create isolated deployments connected to shared resources. They use the sandbox environment but with a unique PROJECT_ENV value.
Configuration
Section titled âConfigurationâSet PROJECT_ENV to a unique value per developer:
- name: Setup Environment uses: ./.github/actions/setup-environment with: project-env: ${{ github.actor }}Branch Naming
Section titled âBranch NamingâPersonal builds trigger on branches not matching static environments:
feat/*branches deploy personal buildsnobuild-*andnobuild/*branches skip deployment
AWS OIDC Configuration
Section titled âAWS OIDC ConfigurationâCreate OIDC Provider
Section titled âCreate OIDC ProviderâIn AWS IAM, create an OIDC identity provider:
- Provider URL:
https://token.actions.githubusercontent.com - Audience:
sts.amazonaws.com
Create IAM Role
Section titled âCreate IAM Roleâ{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::ACCOUNT:oidc-provider/token.actions.githubusercontent.com" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" }, "StringLike": { "token.actions.githubusercontent.com:sub": "repo:ORG/REPO:*" } } } ]}GitHub Environments
Section titled âGitHub EnvironmentsâCreate GitHub environments matching deployment targets:
developmentproductionsandbox
Each environment contains variables and secrets specific to that deployment target.
CDK Stack Names
Section titled âCDK Stack NamesâThe stack-name parameter in deploy workflows must match the stack ID defined in your CDK app:
new AppStack(app, "AppStack"); // "AppStack" is the stack ID# deploy-env-*.yml- name: Deploy CDK Stack uses: ./.github/actions/cdk-deploy with: stack-name: AppStack # Must match the stack ID aboveIf you rename the stack ID (e.g., to âMyProjectAppStackâ for clarity in AWS), update all deploy workflows to match. Mismatched names cause âNo stacks match the name(s)â errors.
Environment-Aware Hostnames
Section titled âEnvironment-Aware HostnamesâJaypie CDK constructs use envHostname() from @jaypie/constructs to determine deployment hostnames based on PROJECT_ENV:
PROJECT_ENV |
Example Hostname |
|---|---|
production |
example.com (apex domain) |
sandbox |
sandbox.example.com |
development |
development.example.com |
personal |
personal.example.com |
How It Works
Section titled âHow It Worksâ- The
setup-environmentaction reads${{ vars.PROJECT_ENV }}from the GitHub environment - This is exported as
PROJECT_ENVto$GITHUB_ENV - CDK constructs read
process.env.PROJECT_ENVviaenvHostname() - For production, the environment prefix is omitted (apex domain)
- For all other environments, the prefix is prepended
Personal Deployments
Section titled âPersonal DeploymentsâWhen CDK_ENV_PERSONAL is set (e.g., for developer-specific environments), envHostname() automatically:
- Prepends the personal name as the leading prefix in the hostname
- Filters
PROJECT_ENVfrom the env position when it matchesCDK_ENV_PERSONAL(since the deploy workflow overrides it)
| Environment Variables | Result |
|---|---|
CDK_ENV_PERSONAL=studio, CDK_ENV_SUBDOMAIN=operations, CDK_ENV_HOSTED_ZONE=sandbox.example.com |
studio.operations.sandbox.example.com |
CDK_ENV_PERSONAL=studio, component=api, CDK_ENV_HOSTED_ZONE=sandbox.example.com |
studio.api.sandbox.example.com |
Using envHostname in Stacks
Section titled âUsing envHostname in Stacksâimport { envHostname, JaypieAppStack, JaypieStaticWebBucket } from "@jaypie/constructs";
export class MyStack extends JaypieAppStack { constructor(scope: Construct, id: string) { super(scope, id);
const zone = process.env.CDK_ENV_HOSTED_ZONE ?? "example.com"; // Returns "example.com" for production, "sandbox.example.com" for sandbox const host = envHostname({ domain: zone });
new JaypieStaticWebBucket(this, "Bucket", { host, zone }); }}AWS Integration
Section titled âAWS IntegrationâUse OIDC authentication with aws-actions/configure-aws-credentials@v6:
permissions: id-token: write contents: read
steps: - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v6 with: role-to-assume: ${{ vars.AWS_ROLE_ARN }} role-session-name: DeployRoleForGitHubSession aws-region: us-east-1Create an IAM role with trust policy for GitHub OIDC provider. Scope the trust policy to your repository.
Datadog Integration
Section titled âDatadog IntegrationâIntegrate Datadog for observability by storing the API key in AWS Secrets Manager:
env: CDK_ENV_DATADOG_API_KEY_ARN: ${{ vars.DATADOG_API_KEY_ARN }}The CDK stack retrieves the key at deploy time and configures Lambda instrumentation.
Linting and Testing
Section titled âLinting and TestingâUse npm run lint and npm run test. Verify the test script uses vitest run or otherwise does not watch. If the script watches, propose changing it or use vitest run directly.
Troubleshooting
Section titled âTroubleshootingâOIDC Authentication Fails
Section titled âOIDC Authentication Failsâ- Verify OIDC provider exists in AWS IAM
- Check role trust policy includes correct repository
- Ensure
id-token: writepermission in workflow
CDK Deploy Fails
Section titled âCDK Deploy Failsâ- Verify AWS credentials configured
- Check stack name matches between CDK and workflow
- Review CloudFormation events for specific errors
Deployment Targets Wrong Hostname
Section titled âDeployment Targets Wrong HostnameâIf sandbox deploys to production URL or vice versa:
- Go to GitHub Settings â Environments
- Select the environment (sandbox, development, production)
- Verify
PROJECT_ENVvariable exists and matches the environment name exactly - Ensure the CDK stack uses
envHostname()rather than hardcoded hostnames
Test Matrix Fails on Specific Node Version
Section titled âTest Matrix Fails on Specific Node Versionâ- Check for version-specific dependencies
- Verify package-lock.json is committed
- Review engine requirements in package.json
Deployment Stuck in Concurrency Queue
Section titled âDeployment Stuck in Concurrency Queueâ- Do not use
cancel-in-progress: true(can leave deployments mid-way) - Wait for previous deployment to complete
- Check GitHub Actions for failed/stuck runs in the same concurrency group
Related
Section titled âRelatedâ- CDK Infrastructure - CDK deployment setup
- Environment Variables - Environment configuration
- Project Structure - Monorepo setup