Basic CI/CD Setup Example
This example demonstrates a simple CI/CD setup with staging and production environments using GitHub Actions and Simple Container.
Overview
This setup provides: - Automatic staging deployment when code is pushed to the main branch - Manual production deployment with approval requirement - Slack notifications for deployment status - Basic secret management for cloud provider credentials
Architecture
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ GitHub Repo │ │ GitHub Actions │ │ Simple Container│
│ │ │ │ │ │
│ Push to main ───┼───▶│ Deploy Staging │───▶│ AWS ECS │
│ │ │ │ │ (Staging) │
│ │ │ │ │ │
│ Manual trigger ─┼───▶│ Deploy Prod │───▶│ AWS ECS │
│ (with approval) │ │ (with approval) │ │ (Production) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│
▼
┌─────────────────┐
│ Slack Channel │
│ (Notifications) │
└─────────────────┘
Project Structure
my-app/
├── .sc/
│ └── stacks/
│ └── my-app/
│ ├── server.yaml # Infrastructure configuration
│ ├── secrets.yaml # Encrypted secrets
│ └── client.yaml # Application configuration
├── .github/
│ └── workflows/
│ ├── deploy-my-app.yml # Generated deployment workflow
│ └── destroy-my-app.yml # Generated cleanup workflow
├── src/ # Application source code
└── README.md
Configuration Files
server.yaml
schemaVersion: 1.0
# Infrastructure configuration
provisioner:
type: pulumi
config:
state-storage:
type: pulumi-cloud
config:
url: https://api.pulumi.com
access-token: ${secret:pulumi-access-token}
# CI/CD configuration
cicd:
type: github-actions
config:
organization: "my-company"
# Environment configurations
environments:
staging:
type: staging
protection: false # No approval required
auto-deploy: true # Deploy automatically on main branch push
runner: "ubuntu-latest"
deploy-flags: ["--skip-preview"] # Skip preview for automated deployment
secrets: ["DATABASE_URL", "API_KEY"] # Which secrets from secrets.yaml are available to this environment
variables: # Non-sensitive environment variables for GitHub Actions workflows
NODE_ENV: "staging"
LOG_LEVEL: "debug"
production:
type: production
protection: true # Require approval
reviewers: ["senior-dev", "team-lead"]
auto-deploy: false # Manual deployment only
runner: "ubuntu-latest"
deploy-flags: ["--skip-preview"]
secrets: ["DATABASE_URL", "API_KEY"] # Which secrets from secrets.yaml are available to this environment
variables: # Non-sensitive environment variables for GitHub Actions workflows
NODE_ENV: "production"
LOG_LEVEL: "warn"
# Notification settings
notifications:
slack:
webhook-url: "${secret:slack-webhook-url}"
enabled: true
# Workflow generation settings
workflow-generation:
enabled: true
templates: ["deploy", "destroy"]
auto-update: true
sc-version: "latest"
# ECS Fargate template - handles VPC, load balancer, and ECS cluster automatically
templates:
main-app:
type: ecs-fargate
config:
credentials: "${auth:aws}"
account: "${auth:aws.projectId}"
# DNS and domain management
resources:
registrar:
type: cloudflare
config:
credentials: "${secret:CLOUDFLARE_API_TOKEN}"
accountId: "${secret:CLOUDFLARE_ACCOUNT_ID}"
zoneName: myapp.com
resources:
staging:
template: main-app
resources: &staging-resources
database:
type: mongodb-atlas
config:
instanceSize: "M10"
region: "US_EAST_1"
cloudProvider: AWS
privateKey: "${secret:MONGODB_ATLAS_PRIVATE_KEY}"
publicKey: "${secret:MONGODB_ATLAS_PUBLIC_KEY}"
production:
template: main-app
resources:
<<: *staging-resources
database:
type: mongodb-atlas
config:
instanceSize: "M30"
region: "US_EAST_1"
cloudProvider: AWS
privateKey: "${secret:MONGODB_ATLAS_PRIVATE_KEY}"
publicKey: "${secret:MONGODB_ATLAS_PUBLIC_KEY}"
backup:
every: 1h
retention: 168h
secrets.yaml
schemaVersion: 1.0
# Cloud provider authentication
auth:
aws:
type: aws-token
config:
account: "123456789012"
accessKey: "${secret:aws-access-key}"
secretAccessKey: "${secret:aws-secret-key}"
region: us-east-1
pulumi:
type: pulumi-token
config:
credentials: "${secret:pulumi-access-token}"
# Secret values (actual values, not environment variables)
values:
# AWS credentials
aws-access-key: your-aws-access-key-here
aws-secret-key: your-aws-secret-key-here
# Pulumi access token
pulumi-access-token: pul-YOUR-PULUMI-ACCESS-TOKEN-HERE
# MongoDB Atlas credentials
MONGODB_ATLAS_PUBLIC_KEY: your-mongodb-public-key-here
MONGODB_ATLAS_PRIVATE_KEY: your-mongodb-private-key-here
# Cloudflare API token and account ID for DNS management
CLOUDFLARE_API_TOKEN: your-cloudflare-api-token-here
CLOUDFLARE_ACCOUNT_ID: 23c5ca78cfb4721d9a603ed695a2623e
# Application secrets
api-key: your-application-api-key-here
# CI/CD notification webhooks
slack-webhook-url: "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK"
client.yaml
schemaVersion: 1.0
# Application deployment configuration
stacks:
staging:
type: cloud-compose
parent: my-company/my-app
config:
# Domain for the staging environment
domain: staging.myapp.com
# Size configuration
size:
cpu: 256
memory: 512
# Scaling configuration
scale:
min: 1
max: 3
policy:
cpu:
max: 70
# Use resources defined in parent stack
uses:
- database
# Environment variables
env:
NODE_ENV: "staging"
PORT: "3000"
BASE_URI: https://staging.myapp.com
# Application secrets from parent resources
secrets:
MONGO_URL: "${resource:database.uri}"
API_KEY: "${secret:api-key}"
production:
type: cloud-compose
parent: my-company/my-app
parentEnv: production
config:
# Domain for the production environment
domain: myapp.com
# Size configuration (higher for production)
size:
cpu: 512
memory: 1024
# Scaling configuration
scale:
min: 2
max: 10
policy:
cpu:
max: 70
# Use resources defined in parent stack
uses:
- database
# Environment variables
env:
NODE_ENV: "production"
PORT: "3000"
BASE_URI: https://myapp.com
# Application secrets from parent resources
secrets:
MONGO_URL: "${resource:database.uri}"
API_KEY: "${secret:api-key}"
GitHub Repository Setup
1. Configure GitHub Secrets
Go to your repository Settings → Secrets and variables → Actions and add:
Required secrets:
- SC_CONFIG - Simple Container configuration with SSH key pair to decrypt repository secrets
Note: All cloud provider credentials, API tokens, and application secrets are managed in .sc/stacks/my-app/secrets.yaml and encrypted using Simple Container's secrets management. GitHub Actions only needs the SC_CONFIG secret to decrypt and access all other secrets.
2. Configure Simple Container Secrets
# Initialize secrets management
sc secrets init
# Add your public key for secrets access
sc secrets allow your-public-key
# Edit secrets file with actual values
# (Replace placeholder values in .sc/stacks/my-app/secrets.yaml with real credentials)
vim .sc/stacks/my-app/secrets.yaml
# Encrypt and hide secrets in repository
sc secrets hide
# Commit encrypted secrets
git add .sc/stacks/my-app/secrets.yaml
git commit -m "Add encrypted secrets configuration"
Create GitHub Secret:
# Generate SC_CONFIG for GitHub Actions
# SC_CONFIG contains your SSH private key and Simple Container configuration
# Get your SSH private key (used for decrypting repository secrets):
cat ~/.ssh/id_rsa
# Copy the private key content and add as SC_CONFIG secret in GitHub repository
# Go to: Settings → Secrets and variables → Actions → New repository secret
# Name: SC_CONFIG
# Value: <paste your SSH private key here>
3. Configure Environments
Go to Settings → Environments and create:
Staging Environment:
- Name: staging
- No protection rules (allows automatic deployment)
Production Environment:
- Name: production
- Enable Required reviewers and add team members
- Optionally set Wait timer (e.g., 10 minutes)
- Configure Deployment branches to restrict to main branch only
Setup Instructions
1. Clone and Configure
# Clone your repository
git clone https://github.com/my-company/my-app.git
cd my-app
# Create Simple Container configuration
mkdir -p .sc/stacks/my-app
2. Add Configuration Files
Copy the configuration files above into your project:
- server.yaml → .sc/stacks/my-app/server.yaml
- secrets.yaml → .sc/stacks/my-app/secrets.yaml
- client.yaml → .sc/stacks/my-app/client.yaml
3. Encrypt Secrets
# Initialize secrets management
sc secrets init
# Add your public key
sc secrets allow your-public-key
# Encrypt the secrets file
sc secrets hide
4. Generate Workflows
# Generate GitHub Actions workflows
sc cicd generate --stack my-app --output .github/workflows/
# Validate the generated configuration
sc cicd validate --stack my-app
5. Commit and Push
# Add all files
git add .
# Commit changes
git commit -m "Add Simple Container CI/CD configuration"
# Push to trigger first deployment
git push origin main
Generated Workflows
Simple Container will generate the following workflow files:
.github/workflows/deploy-my-app.yml
This workflow handles deployment to both staging and production:
name: Deploy My App
on:
push:
branches: [main]
workflow_dispatch:
inputs:
environment:
description: 'Environment to deploy'
required: true
default: 'staging'
type: choice
options: ['staging', 'production']
jobs:
deploy-staging:
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
environment: staging
steps:
- name: Deploy to Staging
uses: simple-container-com/api/.github/actions/deploy@v2025.10.4
with:
stack-name: my-app
environment: staging
sc-config: ${{ secrets.SC_CONFIG }}
deploy-production:
if: github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'production'
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy to Production
uses: simple-container-com/api/.github/actions/deploy@v2025.10.4
with:
stack-name: my-app
environment: production
sc-config: ${{ secrets.SC_CONFIG }}
.github/workflows/destroy-my-app.yml
This workflow handles cleanup and resource destruction:
name: Destroy My App
on:
workflow_dispatch:
inputs:
environment:
description: 'Environment to destroy'
required: true
type: choice
options: ['staging', 'production']
confirm:
description: 'Type "destroy" to confirm'
required: true
jobs:
destroy:
if: github.event.inputs.confirm == 'destroy'
runs-on: ubuntu-latest
environment: ${{ github.event.inputs.environment }}
steps:
- name: Destroy Stack
uses: simple-container-com/api/.github/actions/destroy@v2025.10.4
with:
stack-name: my-app
environment: ${{ github.event.inputs.environment }}
sc-config: ${{ secrets.SC_CONFIG }}
Usage
Automatic Staging Deployment
Push code to the main branch to automatically deploy to staging:
The staging deployment will trigger automatically and you'll receive a Slack notification upon completion.
Manual Production Deployment
- Go to your repository's Actions tab
- Select the Deploy My App workflow
- Click Run workflow
- Select production environment
- Click Run workflow
The deployment will wait for approval from the configured reviewers before proceeding.
Resource Cleanup
To destroy resources in an environment:
- Go to Actions tab
- Select the Destroy My App workflow
- Click Run workflow
- Select the environment to destroy
- Type "destroy" in the confirmation field
- Click Run workflow
Monitoring
Deployment Status
Monitor deployments through: - GitHub Actions - View workflow runs and logs - Slack notifications - Receive status updates in your channel - AWS Console - Monitor ECS services and RDS databases
Health Checks
The application includes health check endpoints:
- Staging: https://staging.my-app.com/health
- Production: https://my-app.com/health
Logs and Metrics
Access application logs through: - CloudWatch Logs - Application and infrastructure logs - ECS Console - Container-level metrics - Application Insights - Custom application metrics
Customization
Adding More Environments
To add a development environment:
# In server.yaml
environments:
development:
type: development
protection: false
auto-deploy: true
runner: "ubuntu-latest"
variables:
NODE_ENV: "development"
LOG_LEVEL: "debug"
Custom Notifications
Add Discord notifications:
# In server.yaml
notifications:
slack: "${secret:slack-webhook-url}"
discord: "${secret:discord-webhook-url}"
Advanced Workflow Triggers
Customize deployment triggers:
# Custom trigger in generated workflow
on:
push:
branches: [main]
paths:
- 'src/**'
- '.sc/**'
- 'Dockerfile'
schedule:
- cron: '0 2 * * 1' # Weekly deployment Monday 2 AM UTC
Troubleshooting
Common Issues
Deployment fails with "AWS credentials not found": - Verify GitHub secrets are properly configured - Check AWS IAM permissions for the access key - Ensure AWS region is correct in server.yaml
Workflow doesn't trigger automatically:
- Check branch protection rules don't block pushes
- Verify workflow file syntax is correct
- Ensure the file is in .github/workflows/ directory
Production deployment hangs on approval: - Check environment protection settings - Ensure reviewers have repository access - Verify reviewers are available to approve
Debug Steps
- Check workflow logs in GitHub Actions tab
- Validate configuration locally:
- Test deployment locally:
- Enable debug logging by adding
ACTIONS_STEP_DEBUG=trueto GitHub secrets
Next Steps
After setting up basic CI/CD:
- Add monitoring - Set up CloudWatch alarms and dashboards
- Implement rollback - Configure automated rollback on health check failures
- Add testing - Integrate automated tests before deployment
- Scale resources - Configure auto-scaling based on metrics
- Custom domains - Set up DNS and SSL certificates
For more advanced setups, check out: - Multi-Stack Deployment - Deploy multiple related stacks - Preview Deployments - PR-based testing environments - Advanced Notifications - Multi-channel alerts