Makefile-Driven Deployment Guide

This guide covers the complete deployment process for SYNDI infrastructure using the Makefile-driven approach. All deployments are managed through make commands that handle build, configuration, and AWS resource creation.

Overview

SYNDI uses a Makefile-driven deployment system that provides:

  • Zero configuration files to maintain - No samconfig.toml needed

  • Pure functional deployment - f(ENV, ORG, parameters) Infrastructure

  • Automatic configuration sync - CloudFormation outputs update configs automatically

  • Built-in testing - Deployment verifies everything works

  • Self-documenting - Command line shows exactly what’s being deployed

Key Deployment Parameters

All deployments require these parameters:

  • ENV: Environment (dev, test, stage, prod)

  • ORG: Organization identifier (required, no default for security)

  • ENABLE_AUTH (optional): Enable Cognito authentication (true/false, default: true)

  • CREATE_BUCKETS (optional): Create S3 buckets (true/false, default: false)

  • ADMIN_USERNAME (optional): Create admin user with this email

  • ADMIN_PASSWORD (optional): Set admin user password

Choosing the Right Deploy Command

Decision Tree

What changed?
├─ Only Python code
│  └─ make rs-deploy-function (30 seconds)
│
├─ Only CloudFormation parameters (ENABLE_AUTH, CREATE_BUCKETS)
│  └─ make rs-deploy-only (1-2 minutes)
│
├─ Python code + infrastructure/config
│  └─ make rs-deploy (5-7 minutes)
│
└─ Adding dependencies to requirements.txt
   └─ make rs-deploy (5-7 minutes - rebuilds layer)

Command Comparison

Command

Build Time

Use When

What It Does

rs-deploy-function

~30 sec

Code changes only

Updates Lambda code directly via AWS API

rs-deploy-only

~1-2 min

Config/parameter changes only

Deploys without rebuilding

rs-deploy

~5-7 min

Code + infrastructure changes

Full SAM build and deploy

Deployment Commands

rs-deploy: Full Build and Deploy

Complete build and deployment process.

Usage:

ENV=stage ORG=myorg make rs-deploy

What it does:

  1. Runs sam build - Rebuilds everything including dependency layer

  2. Packages Lambda function

  3. Uploads to S3

  4. Deploys via CloudFormation

  5. Uploads config to S3

Use when:

  • First deployment to a new environment/org

  • Changed dependencies in backend/layers/dependencies/requirements.txt

  • Changed infrastructure in template.yaml

  • Changed both code AND configuration

Time: 5-7 minutes

Example:

# First deployment with authentication
ENABLE_AUTH=true CREATE_BUCKETS=true \
  ORG=myorg ENV=stage make rs-deploy

rs-deploy-only: Deploy Without Build

Deploys using existing build artifacts.

Usage:

ENV=stage ORG=myorg make rs-deploy-only

What it does:

  1. Skips build step (uses existing .aws-sam-{ENV}-{ORG}/)

  2. Uploads config to S3

  3. Deploys existing build via CloudFormation

  4. Handles ROLLBACK_COMPLETE state automatically

Use when:

  • ONLY changed CloudFormation parameters (e.g., ENABLE_AUTH=true)

  • ONLY changed environment variables in template.yaml

  • Want to redeploy same code with different config

  • Previous deployment failed and you want to retry

Time: 1-2 minutes

Example:

# Enable authentication without rebuilding
ENABLE_AUTH=true ENV=stage ORG=myorg make rs-deploy-only

rs-deploy-function: Quick Lambda Update

Fastest deployment method - updates Lambda code directly.

Usage:

ENV=stage ORG=myorg make rs-deploy-function

What it does:

  1. Creates minimal zip of Python code (no dependencies)

  2. Optionally uploads via S3 if package > 69MB

  3. Updates Lambda function directly via AWS API

  4. Bypasses CloudFormation entirely

Use when:

  • ONLY changed Python code in backend/rawscribe/

  • No infrastructure changes

  • No config changes

  • No dependency changes

  • Want fastest possible deployment

Time: 30 seconds

Example:

# Quick bug fix deployment
ENV=stage ORG=myorg make rs-deploy-function

Complete Deployment Workflow

Initial Organization Deployment

Deploy a new organization for the first time:

# Step 1: Deploy infrastructure with all resources
ENABLE_AUTH=true CREATE_BUCKETS=true \
  ADMIN_USERNAME=admin@myorg.com \
  ADMIN_PASSWORD=SecurePassword2025! \
  ORG=myorg ENV=stage make rs-deploy

# Step 2: Sync configuration files from CloudFormation
make sync-configs ENV=stage ORG=myorg

# Step 3: Review auto-generated configs
git diff infra/.config/webapp/stage-myorg.json
git diff infra/.config/lambda/stage-myorg.json

# Step 4: Upload SOPs to forms bucket
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
aws s3 cp your-sop.yaml \
  s3://rawscribe-forms-stage-myorg-${ACCOUNT_ID}/sops/

# Step 5: Test deployment
ORG=myorg ENV=stage make check-rs

What Gets Created:

  • CloudFormation stack: rawscribe-stage-myorg

  • Lambda function: rawscribe-stage-myorg-backend

  • API Gateway: rawscribe-stage-myorg-api

  • Cognito User Pool: rawscribe-stage-myorg-userpool

  • Cognito User Pool Client

  • Cognito Groups: ADMINS, LAB_MANAGERS, RESEARCHERS, CLINICIANS

  • S3 Buckets:

    • syndi-frontend-stage-myorg-{accountid}

    • rawscribe-lambda-stage-myorg-{accountid}

    • rawscribe-forms-stage-myorg-{accountid}

    • rawscribe-eln-stage-myorg-{accountid}

    • rawscribe-eln-drafts-stage-myorg-{accountid}

  • CloudFront Distribution

  • IAM Roles and Policies

Subsequent Deployments

For updates after initial deployment:

# Quick code update (most common)
ORG=myorg ENV=stage make rs-deploy-function

# Configuration parameter change
ENABLE_AUTH=true ORG=myorg ENV=stage make rs-deploy-only

# Infrastructure change
ORG=myorg ENV=stage make rs-deploy

# Always sync configs after infrastructure changes
make sync-configs ENV=stage ORG=myorg

Production Deployment

Production deployments with confirmation:

# Production deployment (will pause for confirmation)
ENABLE_AUTH=true CREATE_BUCKETS=false \
  ADMIN_USERNAME=admin@myorg.com \
  ADMIN_PASSWORD=ProductionPassword2025! \
  ORG=myorg ENV=prod make rs-deploy

The deployment will automatically:

  • Pause for changeset confirmation (production only)

  • Create admin user if credentials provided

  • Test authentication

  • Test API endpoints

  • Display deployment summary with next steps

Deployment Parameters

ENABLE_AUTH

Controls whether Cognito authentication is created and enforced.

Values: true | false
Default: true

When to use true:

  • Production deployments

  • Staging environments with real users

  • When you need role-based access control

When to use false:

  • Local testing

  • Development environments

  • CI/CD testing pipelines

  • Quick testing without user management

Example:

# Disable auth for testing
ENABLE_AUTH=false ORG=testorg ENV=stage make rs-deploy

# Enable auth for production
ENABLE_AUTH=true ORG=myorg ENV=prod make rs-deploy

CREATE_BUCKETS

Controls whether S3 buckets are created during deployment.

Values: true | false
Default: false

When to use true:

  • First deployment to new environment/org

  • Buckets don’t exist yet

  • Want CloudFormation to manage buckets

When to use false:

  • Buckets already exist

  • Subsequent deployments

  • Redeployment after stack deletion (buckets persisted)

Example:

# First deployment - create buckets
CREATE_BUCKETS=true ORG=neworg ENV=stage make rs-deploy

# Update deployment - buckets exist
CREATE_BUCKETS=false ORG=neworg ENV=stage make rs-deploy

Note: If you set CREATE_BUCKETS=false and buckets don’t exist, deployment will fail. CloudFormation will report missing bucket resources.

ADMIN_USERNAME and ADMIN_PASSWORD

Create an admin user automatically during deployment.

Requirements:

  • Both must be provided together

  • Only works when ENABLE_AUTH=true

  • Username should be a valid email address

  • Password must meet Cognito password requirements

What happens:

  1. Creates Cognito user with username as email

  2. Ensures ADMINS group exists

  3. Adds user to ADMINS group

  4. Sets permanent password (no temp password required)

  5. Tests authentication

  6. Tests API endpoint access

  7. Displays results in deployment summary

Example:

ENABLE_AUTH=true CREATE_BUCKETS=true \
  ADMIN_USERNAME=admin@myorg.com \
  ADMIN_PASSWORD=SecurePass2025! \
  ORG=myorg ENV=stage make rs-deploy

Output:

👤 Creating admin user admin@myorg.com...
👥 Ensuring admin group exists...
🔗 Adding user to admin group...
🔐 Setting permanent password...
🔑 Testing authentication...
✅ Authentication successful!

🧪 Testing API endpoints:
  Health check: "status":"healthy"
  SOPs list: ✅ Found 3 SOPs

════════════════════════════════════════════════
🎉 Deployment Complete: stage/myorg
════════════════════════════════════════════════
📡 API Endpoint: https://abc123.execute-api.us-east-1.amazonaws.com/stage
🔐 User Pool ID: us-east-1_ABC123
🔑 Client ID: abc123def456
👤 Admin User: admin@myorg.com
🔒 Admin Pass: [set successfully]

Build Directory Isolation

Each environment-organization combination gets its own SAM build directory:

.aws-sam-{ENV}-{ORG}/          # Isolated build directory
├── build.toml                 # SAM build metadata
├── cache/                     # Build cache
├── DependencyLayer/           # Python dependencies layer
│   └── python/
│       ├── fastapi/
│       ├── boto3/
│       └── ...
├── RawscribeLambda/          # Application code
│   └── rawscribe/
└── template.yaml             # Processed template

Key Points:

  • Never mix builds between organizations

  • Each org can deploy independently

  • Builds are cached for faster subsequent deployments

  • Manual deletion of build dirs forces clean rebuild

Clean rebuild:

# Remove build directory
rm -rf .aws-sam-stage-myorg/

# Next deploy will rebuild from scratch
ORG=myorg ENV=stage make rs-deploy

Dependency Layer Caching

The dependency layer contains all Python packages from requirements.txt:

When layer is rebuilt:

  • First rs-deploy for an ENV/ORG combination

  • When backend/layers/dependencies/requirements.txt changes

  • When build cache is cleared

When layer is reused:

  • rs-deploy-only (uses cached layer)

  • rs-deploy-function (bypasses layer entirely)

  • Subsequent rs-deploy if requirements.txt unchanged

Force layer rebuild:

# Clear cache
rm -rf .aws-sam-stage-myorg/cache/

# Rebuild layer
ORG=myorg ENV=stage make rs-deploy

Stack State Handling

The deployment automatically handles CloudFormation stack states:

ROLLBACK_COMPLETE State

If a previous deployment failed and the stack is in ROLLBACK_COMPLETE state:

ORG=myorg ENV=stage make rs-deploy-only

Output:

⚠️  Stack rawscribe-stage-myorg is in ROLLBACK_COMPLETE state
🗑️  Deleting failed stack before redeploying...
⏳ Waiting for stack deletion (this may take a minute)...
✅ Failed stack deleted successfully

The deployment automatically:

  1. Detects ROLLBACK_COMPLETE state

  2. Deletes the failed stack

  3. Waits for deletion to complete

  4. Proceeds with fresh deployment

Other Stack States

  • CREATE_COMPLETE: Stack healthy, deployment proceeds

  • UPDATE_COMPLETE: Stack healthy, deployment proceeds

  • UPDATE_IN_PROGRESS: Deployment waits or fails

  • DELETE_IN_PROGRESS: Deployment waits

  • NO_STACK: Fresh deployment proceeds

Check stack status:

ORG=myorg ENV=stage make check-rs-stack-status

Configuration Sync

After deployment, sync configuration files from CloudFormation outputs:

make sync-configs ENV=stage ORG=myorg

This command:

  1. Queries CloudFormation stack outputs

  2. Extracts infrastructure values (API endpoint, Cognito IDs)

  3. Updates infra/.config/webapp/stage-myorg.json

  4. Updates infra/.config/lambda/stage-myorg.json

  5. Preserves custom fields

  6. Displays changes

Always run sync-configs when:

  • First deployment to new org

  • Cognito resources recreated

  • API Gateway endpoint changes

  • Any infrastructure resource IDs change

See Sync Configs Guide for details.

Common Deployment Scenarios

Scenario 1: Fix Python Bug

# 1. Fix bug in backend/rawscribe/
vim backend/rawscribe/routes/sops.py

# 2. Quick deploy (30 seconds)
ORG=myorg ENV=stage make rs-deploy-function

# 3. Verify fix
ORG=myorg ENV=stage make rs-watch-log

Scenario 2: Add Python Dependency

# 1. Add package to requirements.txt
echo "pandas==2.0.0" >> backend/layers/dependencies/requirements.txt

# 2. Full rebuild (layer changed)
ORG=myorg ENV=stage make rs-deploy

# 3. Verify package available
ORG=myorg ENV=stage make rs-watch-log

Scenario 3: Enable Authentication

# 1. Deploy with authentication enabled
ENABLE_AUTH=true ORG=myorg ENV=stage make rs-deploy-only

# 2. Sync configs
make sync-configs ENV=stage ORG=myorg

# 3. Create users
# (users created via Cognito console or AWS CLI)

Scenario 4: Change File Upload Limit

# 1. Edit config file
vim infra/.config/lambda/stage-myorg.json
# Change: "max_file_size_mb": 50

# 2. Deploy config change
ORG=myorg ENV=stage make rs-deploy-only

Scenario 5: Deploy to Multiple Organizations

# Deploy to organization 1
ORG=org1 ENV=stage ENABLE_AUTH=true make rs-deploy
make sync-configs ENV=stage ORG=org1

# Deploy to organization 2 (parallel)
ORG=org2 ENV=stage ENABLE_AUTH=true make rs-deploy
make sync-configs ENV=stage ORG=org2

# Both deployments are completely isolated

Deployment Verification

Check Deployment Status

# Quick status check
ORG=myorg ENV=stage make check-rs

Output:

=== myorg Resources (stage) ===
Lambda:      rawscribe-stage-myorg-backend
API Gateway: rawscribe-stage-myorg-api
API Endpoint: https://abc123.execute-api.us-east-1.amazonaws.com/stage/
Stack Name:  rawscribe-stage-myorg
User Pool:   us-east-1_ABC123
Client ID:   abc123def456
S3 Buckets:
     lambda:     rawscribe-lambda-stage-myorg-123456789
     forms:      rawscribe-forms-stage-myorg-123456789
     ELN:        rawscribe-eln-stage-myorg-123456789
     ELN drafts: rawscribe-eln-drafts-stage-myorg-123456789

Test Endpoints

# Get API endpoint
API_ENDPOINT=$(aws cloudformation describe-stacks \
  --stack-name rawscribe-stage-myorg \
  --query 'Stacks[0].Outputs[?OutputKey==`ApiEndpoint`].OutputValue' \
  --output text)

# Test health endpoint
curl ${API_ENDPOINT}/health

# Expected: {"status":"healthy","service":"claire-api",...}

Test Authentication

# Automated JWT testing
ORG=myorg ENV=stage make test-jwt-aws

# Manual testing
TOKEN=$(make get-rs-token ENV=stage ORG=myorg \
  USER_NAME=admin@myorg.com PASSWORD=YourPassword)

curl -H "Authorization: Bearer ${TOKEN}" \
  ${API_ENDPOINT}/api/v1/sops/list

View Logs

# Tail Lambda logs in real-time
ORG=myorg ENV=stage make rs-watch-log

# View recent logs
aws logs tail /aws/lambda/rawscribe-stage-myorg-backend \
  --since 10m --follow

Troubleshooting

Deployment Fails with “Stack does not exist”

Cause: First deployment or stack was deleted

Solution:

# Use rs-deploy (not rs-deploy-only) for first deployment
ORG=myorg ENV=stage make rs-deploy

Deployment Fails with “Bucket already exists”

Cause: CREATE_BUCKETS=true but buckets already exist

Solution:

# Use CREATE_BUCKETS=false for existing buckets
CREATE_BUCKETS=false ORG=myorg ENV=stage make rs-deploy

Deployment Stuck in ROLLBACK_COMPLETE

Cause: Previous deployment failed

Solution:

# rs-deploy-only automatically handles this
ORG=myorg ENV=stage make rs-deploy-only

Lambda Function Not Updated

Cause: Using cached build or wrong command

Solution:

# Force clean rebuild
rm -rf .aws-sam-stage-myorg/
ORG=myorg ENV=stage make rs-deploy

# Or use direct function update
ORG=myorg ENV=stage make rs-deploy-function

Config Changes Not Applied

Cause: Forgot to run sync-configs or deploy

Solution:

# Sync configs from CloudFormation
make sync-configs ENV=stage ORG=myorg

# Deploy config changes
ORG=myorg ENV=stage make rs-deploy-only

Permission Denied Errors

Cause: AWS credentials insufficient

Solution:

# Check AWS credentials
aws sts get-caller-identity

# Ensure your IAM user/role has:
# - CloudFormation full access
# - Lambda full access
# - S3 full access
# - Cognito full access
# - API Gateway full access
# - IAM role creation

Performance Tips

Speed Up Deployments

  1. Use the right command:

    • Code changes → rs-deploy-function (30 sec)

    • Config changes → rs-deploy-only (1-2 min)

    • Full changes → rs-deploy (5-7 min)

  2. Keep dependencies stable:

    • Pin versions in requirements.txt

    • Only update when necessary

    • Layer rebuilds are expensive (5+ min)

  3. Use build caching:

    • Don’t delete build directories unnecessarily

    • SAM automatically uses --cached flag

    • Cache speeds up subsequent builds

  4. Parallel deployments:

    • Different orgs can deploy simultaneously

    • Each uses its own build directory

    • No conflicts between deployments