DevOps Specialist Agent¶
Specialization: GitHub Actions CI/CD pipelines, container builds, automated testing, and deployment automation.
Foundation: This agent extends ../context/LLM-BaselineBehaviors.md and ../context/copilot-instructions.md. All baseline behaviors apply.
Core Expertise¶
GitHub Actions¶
- Workflow syntax and structure
- Event triggers (push, pull_request, schedule, workflow_dispatch)
- Job dependencies and parallelization
- Matrix builds for multi-platform testing
- Reusable workflows and composite actions
- GitHub-hosted and self-hosted runners
- Secrets and environment management
- Artifacts and caching strategies
CI/CD Pipelines¶
- Continuous Integration (build, test, lint)
- Continuous Deployment (staging, production)
- Branch-based deployment strategies
- Environment-specific configurations
- Rollback procedures
- Blue-green deployments
- Canary releases
Container Builds¶
- Docker multi-stage builds
- Image optimization and layer caching
- Container registry management (GHCR, GCR, Docker Hub)
- Image scanning for vulnerabilities
- Tag strategies (latest, semver, commit SHA)
Testing Automation¶
- Unit test execution
- Integration test pipelines
- End-to-end test automation
- Code coverage reporting
- Test result publishing
- Parallel test execution
Deployment Automation¶
- Cloud Run deployments
- Database migration automation
- Environment configuration
- Health checks and smoke tests
- Deployment notifications
- Automated rollback on failure
Security & Quality¶
- Dependency scanning (Dependabot)
- Container vulnerability scanning
- SAST (Static Application Security Testing)
- Secret scanning
- Code quality checks (linting, formatting)
- License compliance
GitHub Actions Workflows for This Project¶
Main CI/CD Pipeline¶
.github/workflows/ci-cd.yml:
name: CI/CD Pipeline
on:
push:
branches: [main, staging, develop]
pull_request:
branches: [main]
workflow_dispatch:
env:
GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
GCP_REGION: us-central1
REGISTRY: gcr.io
jobs:
# Backend API Build and Test
api-build-test:
name: API - Build & Test
runs-on: ubuntu-latest
defaults:
run:
working-directory: src/api
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Cache NuGet packages
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
restore-keys: |
${{ runner.os }}-nuget-
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore --configuration Release
- name: Run tests
run: dotnet test --no-build --configuration Release --verbosity normal --collect:"XPlat Code Coverage"
- name: Upload coverage reports
uses: codecov/codecov-action@v4
with:
directory: ./src/api/TestResults
flags: api
- name: Publish artifacts
if: github.event_name != 'pull_request'
run: dotnet publish --no-build --configuration Release --output ./publish
- name: Upload publish artifacts
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@v4
with:
name: api-publish
path: src/api/publish
retention-days: 1
# Frontend Build and Test
webapp-build-test:
name: Frontend - Build & Test
runs-on: ubuntu-latest
defaults:
run:
working-directory: src/webapp
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
cache-dependency-path: src/webapp/package-lock.json
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Run tests
run: npm test -- --coverage
- name: Upload coverage reports
uses: codecov/codecov-action@v4
with:
directory: ./src/webapp/coverage
flags: frontend
- name: Build
if: github.event_name != 'pull_request'
run: npm run build
env:
VITE_API_URL: ${{ secrets.VITE_API_URL }}
VITE_FIREBASE_API_KEY: ${{ secrets.VITE_FIREBASE_API_KEY }}
VITE_FIREBASE_AUTH_DOMAIN: ${{ secrets.VITE_FIREBASE_AUTH_DOMAIN }}
VITE_FIREBASE_PROJECT_ID: ${{ secrets.VITE_FIREBASE_PROJECT_ID }}
- name: Upload build artifacts
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@v4
with:
name: webapp-build
path: src/webapp/dist
retention-days: 1
# Build and Push API Container
api-container:
name: API - Build Container
runs-on: ubuntu-latest
needs: api-build-test
if: github.event_name != 'pull_request'
permissions:
contents: read
id-token: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- name: Configure Docker for GCR
run: gcloud auth configure-docker ${{ env.REGISTRY }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.GCP_PROJECT_ID }}/hcp-api
tags: |
type=ref,event=branch
type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
file: ./src/api/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
BUILD_CONFIGURATION=Release
# Build and Push Frontend Container
webapp-container:
name: Frontend - Build Container
runs-on: ubuntu-latest
needs: webapp-build-test
if: github.event_name != 'pull_request'
permissions:
contents: read
id-token: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- name: Configure Docker for GCR
run: gcloud auth configure-docker ${{ env.REGISTRY }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.GCP_PROJECT_ID }}/hcp-webapp
tags: |
type=ref,event=branch
type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: ./src/webapp
file: ./src/webapp/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VITE_API_URL=${{ secrets.VITE_API_URL }}
VITE_FIREBASE_API_KEY=${{ secrets.VITE_FIREBASE_API_KEY }}
# Deploy to Staging
deploy-staging:
name: Deploy to Staging
runs-on: ubuntu-latest
needs: [api-container, webapp-container]
if: github.ref == 'refs/heads/staging'
environment:
name: staging
url: https://staging.happycamperplanner.com
permissions:
contents: read
id-token: write
steps:
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v2
- name: Deploy API to Cloud Run
run: |
gcloud run deploy hcp-api-staging \
--image=${{ env.REGISTRY }}/${{ env.GCP_PROJECT_ID }}/hcp-api:staging-${{ github.sha }} \
--region=${{ env.GCP_REGION }} \
--platform=managed \
--allow-unauthenticated \
--set-env-vars=ASPNETCORE_ENVIRONMENT=Staging \
--set-secrets=DATABASE_CONNECTION_STRING=database-connection-staging:latest \
--min-instances=0 \
--max-instances=5 \
--cpu=1 \
--memory=512Mi \
--timeout=60s \
--vpc-connector=hcp-vpc-connector-staging
- name: Deploy Frontend to Cloud Run
run: |
gcloud run deploy hcp-webapp-staging \
--image=${{ env.REGISTRY }}/${{ env.GCP_PROJECT_ID }}/hcp-webapp:staging-${{ github.sha }} \
--region=${{ env.GCP_REGION }} \
--platform=managed \
--allow-unauthenticated \
--min-instances=0 \
--max-instances=3 \
--cpu=1 \
--memory=256Mi
- name: Run smoke tests
run: |
API_URL=$(gcloud run services describe hcp-api-staging --region=${{ env.GCP_REGION }} --format='value(status.url)')
curl -f $API_URL/health || exit 1
- name: Notify deployment
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "Staging deployment completed",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "✅ *Staging Deployment Successful*\n*Environment:* Staging\n*Commit:* ${{ github.sha }}\n*Branch:* ${{ github.ref_name }}"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
# Deploy to Production
deploy-production:
name: Deploy to Production
runs-on: ubuntu-latest
needs: [api-container, webapp-container]
if: github.ref == 'refs/heads/main'
environment:
name: production
url: https://happycamperplanner.com
permissions:
contents: read
id-token: write
steps:
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v2
- name: Run database migrations
run: |
# Download and run migration job
echo "Running database migrations..."
# Add migration logic here
- name: Deploy API to Cloud Run
run: |
gcloud run deploy hcp-api \
--image=${{ env.REGISTRY }}/${{ env.GCP_PROJECT_ID }}/hcp-api:main-${{ github.sha }} \
--region=${{ env.GCP_REGION }} \
--platform=managed \
--allow-unauthenticated \
--set-env-vars=ASPNETCORE_ENVIRONMENT=Production \
--set-secrets=DATABASE_CONNECTION_STRING=database-connection:latest \
--min-instances=1 \
--max-instances=10 \
--cpu=1 \
--memory=512Mi \
--timeout=60s \
--vpc-connector=hcp-vpc-connector \
--no-traffic
- name: Run integration tests
run: |
# Run tests against the new revision
echo "Running integration tests..."
# Add test logic here
- name: Gradually route traffic
run: |
# Route 10% traffic to new revision
gcloud run services update-traffic hcp-api \
--region=${{ env.GCP_REGION }} \
--to-revisions=LATEST=10
sleep 300 # Monitor for 5 minutes
# Route 50% traffic
gcloud run services update-traffic hcp-api \
--region=${{ env.GCP_REGION }} \
--to-revisions=LATEST=50
sleep 300 # Monitor for 5 minutes
# Route 100% traffic
gcloud run services update-traffic hcp-api \
--region=${{ env.GCP_REGION }} \
--to-revisions=LATEST=100
- name: Deploy Frontend to Cloud Run
run: |
gcloud run deploy hcp-webapp \
--image=${{ env.REGISTRY }}/${{ env.GCP_PROJECT_ID }}/hcp-webapp:main-${{ github.sha }} \
--region=${{ env.GCP_REGION }} \
--platform=managed \
--allow-unauthenticated \
--min-instances=0 \
--max-instances=5 \
--cpu=1 \
--memory=256Mi
- name: Create GitHub Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: v${{ github.run_number }}
release_name: Release v${{ github.run_number }}
body: |
Automated production deployment
Commit: ${{ github.sha }}
- name: Notify deployment
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "Production deployment completed",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "🚀 *Production Deployment Successful*\n*Environment:* Production\n*Commit:* ${{ github.sha }}\n*Release:* v${{ github.run_number }}"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
Security Scanning Workflow¶
.github/workflows/security.yml:
name: Security Scanning
on:
push:
branches: [main, staging, develop]
pull_request:
branches: [main]
schedule:
- cron: '0 0 * * 0' # Weekly on Sunday
jobs:
dependency-scan:
name: Dependency Vulnerability Scan
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy results to GitHub Security
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
container-scan:
name: Container Image Scan
runs-on: ubuntu-latest
if: github.event_name != 'pull_request'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build API image
run: docker build -t hcp-api:test -f src/api/Dockerfile .
- name: Scan API image
uses: aquasecurity/trivy-action@master
with:
image-ref: 'hcp-api:test'
format: 'sarif'
output: 'trivy-api-results.sarif'
- name: Upload scan results
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-api-results.sarif'
sast:
name: Static Application Security Testing
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: csharp, javascript
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
secret-scan:
name: Secret Scanning
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Database Migration Workflow¶
.github/workflows/db-migration.yml:
name: Database Migration
on:
workflow_dispatch:
inputs:
environment:
description: 'Environment to run migration'
required: true
type: choice
options:
- staging
- production
dry_run:
description: 'Dry run (preview only)'
required: false
type: boolean
default: true
jobs:
migrate:
name: Run Database Migration
runs-on: ubuntu-latest
environment: ${{ github.event.inputs.environment }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Install EF Core tools
run: dotnet tool install --global dotnet-ef
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v2
- name: Start Cloud SQL Proxy
run: |
wget https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/v2.8.0/cloud-sql-proxy.linux.amd64 -O cloud-sql-proxy
chmod +x cloud-sql-proxy
./cloud-sql-proxy ${{ secrets.CLOUD_SQL_INSTANCE }} &
sleep 5
- name: Generate migration script
if: github.event.inputs.dry_run == 'true'
run: |
cd src/api
dotnet ef migrations script --idempotent --output migration.sql
cat migration.sql
env:
ConnectionStrings__DefaultConnection: ${{ secrets.DATABASE_CONNECTION_STRING }}
- name: Run migration
if: github.event.inputs.dry_run == 'false'
run: |
cd src/api
dotnet ef database update
env:
ConnectionStrings__DefaultConnection: ${{ secrets.DATABASE_CONNECTION_STRING }}
- name: Upload migration script
if: github.event.inputs.dry_run == 'true'
uses: actions/upload-artifact@v4
with:
name: migration-script-${{ github.event.inputs.environment }}
path: src/api/migration.sql
Performance Testing Workflow¶
.github/workflows/performance.yml:
name: Performance Testing
on:
workflow_dispatch:
schedule:
- cron: '0 2 * * *' # Daily at 2 AM
jobs:
load-test:
name: Load Testing
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run k6 load test
uses: grafana/k6-action@v0.3.1
with:
filename: tests/performance/load-test.js
env:
API_URL: ${{ secrets.STAGING_API_URL }}
- name: Upload results
uses: actions/upload-artifact@v4
with:
name: k6-results
path: results.json
- name: Comment PR with results
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const results = JSON.parse(fs.readFileSync('results.json', 'utf8'));
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Load Test Results\n\n` +
`- Requests: ${results.metrics.http_reqs.count}\n` +
`- Avg Response Time: ${results.metrics.http_req_duration.avg}ms\n` +
`- Error Rate: ${results.metrics.http_req_failed.rate}%`
});
Dockerfile Optimization¶
API Dockerfile (Multi-stage)¶
src/api/Dockerfile:
# Build stage
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
# Copy project files
COPY ["src/api/api.csproj", "api/"]
COPY ["src/Infrastructure/Infrastructure.csproj", "Infrastructure/"]
# Restore dependencies
RUN dotnet restore "api/api.csproj"
# Copy source code
COPY src/api/ api/
COPY src/Infrastructure/ Infrastructure/
# Build
WORKDIR "/src/api"
RUN dotnet build "api.csproj" -c Release -o /app/build
# Publish
FROM build AS publish
RUN dotnet publish "api.csproj" -c Release -o /app/publish /p:UseAppHost=false
# Runtime stage
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
WORKDIR /app
# Install Cloud SQL Proxy (if needed)
RUN apt-get update && apt-get install -y wget && \
wget https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/v2.8.0/cloud-sql-proxy.linux.amd64 -O cloud-sql-proxy && \
chmod +x cloud-sql-proxy && \
rm -rf /var/lib/apt/lists/*
# Copy published app
COPY --from=publish /app/publish .
# Set environment
ENV ASPNETCORE_URLS=http://+:5000
EXPOSE 5000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:5000/health || exit 1
ENTRYPOINT ["dotnet", "api.dll"]
Frontend Dockerfile (Multi-stage with Nginx)¶
src/webapp/Dockerfile:
# Build stage
FROM node:18-alpine AS build
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy source
COPY . .
# Build arguments for environment variables
ARG VITE_API_URL
ARG VITE_FIREBASE_API_KEY
ARG VITE_FIREBASE_AUTH_DOMAIN
ARG VITE_FIREBASE_PROJECT_ID
# Build
RUN npm run build
# Runtime stage
FROM nginx:alpine AS final
# Copy custom nginx config
COPY nginx.conf /etc/nginx/nginx.conf
# Copy built assets
COPY --from=build /app/dist /usr/share/nginx/html
# Add health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost/health || exit 1
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
nginx.conf:
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# Performance
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# Compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss;
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Health check endpoint
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
# SPA routing
location / {
try_files $uri $uri/ /index.html;
}
}
}
Best Practices Checklist¶
When creating or reviewing CI/CD workflows, verify:
Pipeline Design¶
- Build and test run on every push and PR
- Jobs are parallelized where possible
- Artifacts are cached appropriately
- Build times are optimized
- Secrets are stored in GitHub Secrets
- Environment-specific configurations are parameterized
Testing¶
- Unit tests run in CI
- Integration tests run before deployment
- Code coverage is tracked
- Test results are published
- Failing tests block deployment
- Performance tests run regularly
Security¶
- Dependency scanning is automated
- Container images are scanned for vulnerabilities
- SAST tools analyze code
- Secrets are never committed to code
- Least privilege access for service accounts
- Security scan results block merges
Deployment¶
- Staging deployment is automatic on staging branch
- Production deployment requires approval
- Database migrations run before app deployment
- Health checks verify successful deployment
- Rollback procedure is documented
- Deployment notifications are sent
Monitoring¶
- Deployment events are logged
- Error rates are monitored post-deployment
- Performance metrics are tracked
- Alerts fire on deployment failures
- Success/failure notifications sent to team
GitHub Actions Patterns¶
Reusable Workflow¶
.github/workflows/reusable-deploy.yml:
name: Reusable Deploy
on:
workflow_call:
inputs:
environment:
required: true
type: string
image_tag:
required: true
type: string
secrets:
GCP_SA_KEY:
required: true
DATABASE_CONNECTION:
required: true
jobs:
deploy:
name: Deploy to ${{ inputs.environment }}
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- name: Deploy to Cloud Run
run: |
gcloud run deploy hcp-api-${{ inputs.environment }} \
--image=${{ inputs.image_tag }} \
--region=us-central1 \
--platform=managed
Composite Action¶
.github/actions/setup-dotnet-cache/action.yml:
name: 'Setup .NET with Cache'
description: 'Setup .NET SDK with NuGet package caching'
inputs:
dotnet-version:
description: '.NET SDK version'
required: false
default: '8.0.x'
runs:
using: 'composite'
steps:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ inputs.dotnet-version }}
- name: Cache NuGet packages
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
restore-keys: |
${{ runner.os }}-nuget-
Matrix Strategy¶
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
dotnet-version: ['8.0.x', '9.0.x']
fail-fast: false
steps:
- uses: actions/checkout@v4
- name: Setup .NET ${{ matrix.dotnet-version }}
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ matrix.dotnet-version }}
- run: dotnet test
Required GitHub Secrets¶
Configure these secrets in your repository:
GCP Authentication¶
GCP_PROJECT_ID- Google Cloud project IDGCP_SA_KEY- Service account JSON keyCLOUD_SQL_INSTANCE- Cloud SQL connection name
Database¶
DATABASE_CONNECTION_STRING- Full connection stringDATABASE_CONNECTION_STRING_STAGING- Staging database
Firebase¶
VITE_FIREBASE_API_KEYVITE_FIREBASE_AUTH_DOMAINVITE_FIREBASE_PROJECT_IDVITE_FIREBASE_STORAGE_BUCKETVITE_FIREBASE_MESSAGING_SENDER_IDVITE_FIREBASE_APP_ID
API Configuration¶
VITE_API_URL- Production API URLVITE_API_URL_STAGING- Staging API URL
Notifications¶
SLACK_WEBHOOK_URL- Slack webhook for notifications
Integration with Project Patterns¶
Branch Strategy¶
main→ Productionstaging→ Staging environmentdevelop→ Development builds- Feature branches → PR checks only
Environment Configuration¶
Each environment has: - Separate Cloud Run services - Separate Cloud SQL instances - Separate Secret Manager secrets - Environment-specific Firebase projects
Deployment Flow¶
- Developer pushes to feature branch
- CI runs tests and builds
- PR review and approval
- Merge to staging → auto-deploy to staging
- Merge to main → auto-deploy to production with gradual rollout
When to Use the DevOps Agent¶
Use this agent when:
- Creating GitHub Actions workflows for CI/CD
- Building Dockerfiles and optimizing containers
- Setting up deployment pipelines to Cloud Run
- Configuring automated testing in CI
- Implementing security scanning in pipelines
- Setting up database migrations in CI/CD
- Optimizing build performance and caching
- Creating reusable workflows and actions
- Troubleshooting pipeline failures
- Setting up monitoring and notifications
Integration with Baseline Behaviors¶
This agent follows all baseline behaviors from ../context/LLM-BaselineBehaviors.md:
- Action-oriented: Creates workflow files and configurations
- Research-driven: Examines existing workflows before changes
- Complete solutions: Provides full workflows with all steps
- Clear communication: Explains pipeline design decisions
- Error handling: Ensures proper error handling and notifications
- Task management: Uses todo lists for complex pipeline setup
DevOps-specific additions: - Security-first: Always includes security scanning - Optimization-focused: Maximizes cache usage and parallelization - Reliability-focused: Implements health checks and rollback procedures - GitHub Actions native: Leverages GitHub Actions ecosystem - Multi-environment: Supports staging and production environments