-
Notifications
You must be signed in to change notification settings - Fork 464
Kasper/feature 2 #1073
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Kasper/feature 2 #1073
Changes from all commits
c71c12a
ef94b81
d48fe29
cfb8cde
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,8 +3,8 @@ on: | |||||||||||||||||||||||||||||||
| push: | ||||||||||||||||||||||||||||||||
| branches: | ||||||||||||||||||||||||||||||||
| - main | ||||||||||||||||||||||||||||||||
| - dev | ||||||||||||||||||||||||||||||||
| pull_request: {} | ||||||||||||||||||||||||||||||||
| pull_request: | ||||||||||||||||||||||||||||||||
| types: [opened, reopened, synchronize, closed] | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| concurrency: | ||||||||||||||||||||||||||||||||
| group: ${{ github.workflow }}-${{ github.ref }} | ||||||||||||||||||||||||||||||||
|
|
@@ -14,14 +14,17 @@ permissions: | |||||||||||||||||||||||||||||||
| actions: write | ||||||||||||||||||||||||||||||||
| contents: read | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| env: | ||||||||||||||||||||||||||||||||
| FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} | ||||||||||||||||||||||||||||||||
| # Change this if you want to deploy to a different org | ||||||||||||||||||||||||||||||||
| FLY_ORG: personal | ||||||||||||||||||||||||||||||||
| jobs: | ||||||||||||||||||||||||||||||||
| lint: | ||||||||||||||||||||||||||||||||
| name: ⬣ ESLint | ||||||||||||||||||||||||||||||||
| runs-on: ubuntu-22.04 | ||||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||
| - name: ⬇️ Checkout repo | ||||||||||||||||||||||||||||||||
| uses: actions/checkout@v4 | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| - name: ⎔ Setup node | ||||||||||||||||||||||||||||||||
| uses: actions/setup-node@v4 | ||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||
|
|
@@ -45,7 +48,6 @@ jobs: | |||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||
| - name: ⬇️ Checkout repo | ||||||||||||||||||||||||||||||||
| uses: actions/checkout@v4 | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| - name: ⎔ Setup node | ||||||||||||||||||||||||||||||||
| uses: actions/setup-node@v4 | ||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||
|
|
@@ -72,7 +74,6 @@ jobs: | |||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||
| - name: ⬇️ Checkout repo | ||||||||||||||||||||||||||||||||
| uses: actions/checkout@v4 | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| - name: ⎔ Setup node | ||||||||||||||||||||||||||||||||
| uses: actions/setup-node@v4 | ||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||
|
|
@@ -97,7 +98,6 @@ jobs: | |||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||
| - name: ⬇️ Checkout repo | ||||||||||||||||||||||||||||||||
| uses: actions/checkout@v4 | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| - name: 🏄 Copy test env vars | ||||||||||||||||||||||||||||||||
| run: cp .env.example .env | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
@@ -146,8 +146,7 @@ jobs: | |||||||||||||||||||||||||||||||
| container: | ||||||||||||||||||||||||||||||||
| name: 📦 Prepare Container | ||||||||||||||||||||||||||||||||
| runs-on: ubuntu-24.04 | ||||||||||||||||||||||||||||||||
| # only prepare container on pushes | ||||||||||||||||||||||||||||||||
| if: ${{ github.event_name == 'push' }} | ||||||||||||||||||||||||||||||||
| if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} | ||||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||
| - name: ⬇️ Checkout repo | ||||||||||||||||||||||||||||||||
| uses: actions/checkout@v4 | ||||||||||||||||||||||||||||||||
|
|
@@ -164,37 +163,80 @@ jobs: | |||||||||||||||||||||||||||||||
| - name: 🎈 Setup Fly | ||||||||||||||||||||||||||||||||
| uses: superfly/flyctl-actions/[email protected] | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| - name: 📦 Build Staging Container | ||||||||||||||||||||||||||||||||
| if: ${{ github.ref == 'refs/heads/dev' }} | ||||||||||||||||||||||||||||||||
| - name: 📦 Build Production Container | ||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||
| flyctl deploy \ | ||||||||||||||||||||||||||||||||
| --build-only \ | ||||||||||||||||||||||||||||||||
| --push \ | ||||||||||||||||||||||||||||||||
| --image-label ${{ github.sha }} \ | ||||||||||||||||||||||||||||||||
| --build-arg COMMIT_SHA=${{ github.sha }} \ | ||||||||||||||||||||||||||||||||
| --app ${{ steps.app_name.outputs.value }}-staging | ||||||||||||||||||||||||||||||||
| env: | ||||||||||||||||||||||||||||||||
| FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} | ||||||||||||||||||||||||||||||||
| --build-secret SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} \ | ||||||||||||||||||||||||||||||||
| --app ${{ steps.app_name.outputs.value }} | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| - name: 📦 Build Production Container | ||||||||||||||||||||||||||||||||
| if: ${{ github.ref == 'refs/heads/main' }} | ||||||||||||||||||||||||||||||||
| deploy-staging: | ||||||||||||||||||||||||||||||||
| name: 🚁 Deploy staging app for PR | ||||||||||||||||||||||||||||||||
| runs-on: ubuntu-24.04 | ||||||||||||||||||||||||||||||||
| outputs: | ||||||||||||||||||||||||||||||||
| url: ${{ steps.deploy.outputs.url }} | ||||||||||||||||||||||||||||||||
| environment: | ||||||||||||||||||||||||||||||||
| name: staging | ||||||||||||||||||||||||||||||||
| url: ${{ steps.deploy.outputs.url }} | ||||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||
| - name: ⬇️ Checkout repo | ||||||||||||||||||||||||||||||||
| uses: actions/checkout@v4 | ||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||
| fetch-depth: '50' | ||||||||||||||||||||||||||||||||
| - name: 👀 Read app name | ||||||||||||||||||||||||||||||||
| uses: SebRollen/[email protected] | ||||||||||||||||||||||||||||||||
| id: app_name | ||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||
| file: 'fly.toml' | ||||||||||||||||||||||||||||||||
| field: 'app' | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| - name: 🎈 Setup Fly | ||||||||||||||||||||||||||||||||
| uses: superfly/flyctl-actions/[email protected] | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| - name: 🚁️ Deploy PR app to Fly.io | ||||||||||||||||||||||||||||||||
| if: ${{ github.event.action != 'closed' && env.FLY_API_TOKEN }} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||
| flyctl deploy \ | ||||||||||||||||||||||||||||||||
| --build-only \ | ||||||||||||||||||||||||||||||||
| --push \ | ||||||||||||||||||||||||||||||||
| FLY_APP_NAME="${{ steps.app_name.outputs.value }}-pr-${{ github.event.number }}" | ||||||||||||||||||||||||||||||||
| FLY_REGION=$(flyctl config show | jq -r '.primary_region') | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Create app if it doesn't exist | ||||||||||||||||||||||||||||||||
| if ! flyctl status --app "$FLY_APP_NAME"; then | ||||||||||||||||||||||||||||||||
| # change org name if needed | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
| # change org name if needed | |
| # ensure FLY_ORG (configured at the top of this workflow) is set to the desired org |
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The staging deployment is missing the volume creation step. The production setup requires a persistent volume for the SQLite database (created via fly volumes create), but the staging deployment workflow doesn't create this volume. Without it, the staging app will likely fail to start or lose data between deploys. The volume should be created if the app doesn't exist, similar to how consul and storage are set up.
| flyctl storage create --app $FLY_APP_NAME --name epic-stack-$FLY_APP_NAME --yes > /dev/null 2>&1 | |
| flyctl storage create --app $FLY_APP_NAME --name epic-stack-$FLY_APP_NAME --yes > /dev/null 2>&1 | |
| # Create persistent volume for SQLite database | |
| flyctl volumes create data --region "$FLY_REGION" --app "$FLY_APP_NAME" --yes |
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Secrets are being set during the initial app creation, but if the app already exists and the command fails (line 206), the secrets will never be set. This could happen if an app was manually created or if there was a previous failed deployment. Consider checking if secrets need to be set even for existing apps, or handle the case where the app exists but secrets might be missing.
| # Ensure required secrets are set even if the app already existed or a previous deployment failed | |
| REQUIRED_SECRETS=(SESSION_SECRET HONEYPOT_SECRET SENTRY_DSN RESEND_API_KEY) | |
| MISSING_REQUIRED_SECRET=false | |
| for secret in "${REQUIRED_SECRETS[@]}"; do | |
| if ! flyctl secrets list --app "$FLY_APP_NAME" --json | jq -r '.[].Name' | grep -qx "$secret"; then | |
| MISSING_REQUIRED_SECRET=true | |
| break | |
| fi | |
| done | |
| if [ "$MISSING_REQUIRED_SECRET" = true ]; then | |
| flyctl secrets --app $FLY_APP_NAME set SESSION_SECRET=$(openssl rand -hex 32) HONEYPOT_SECRET=$(openssl rand -hex 32) SENTRY_DSN=${{ secrets.SENTRY_DSN }} RESEND_API_KEY=${{ secrets.RESEND_API_KEY }} | |
| fi |
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a syntax error in this line. The comment is placed inline with the backslash continuation character, which will cause the shell command to fail. The comment should either be on its own line before this command or removed entirely.
| flyctl deploy . \ | |
| --ha=false \ # use only one machine for staging | |
| # use only one machine for staging | |
| flyctl deploy . \ | |
| --ha=false \ |
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The deploy-staging job doesn't populate the url output that it declares on line 180. This output is referenced in the environment section on line 183, but it's never set using the steps.deploy.outputs syntax. You should set this output, likely after the deployment completes successfully.
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable reference "$app" is incorrect. It should be "$FLY_APP_NAME" to match the variable defined on line 229.
| flyctl apps destroy "$app" -y || true | |
| flyctl apps destroy "$FLY_APP_NAME" -y || true |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| # Per-PR Staging Environments | ||
|
|
||
| Date: 2025-12-24 | ||
|
|
||
| Status: accepted | ||
|
|
||
| ## Context | ||
|
|
||
| The Epic Stack previously used a single shared staging environment deployed from the `dev` branch. This approach created several challenges for teams working with multiple pull requests: | ||
|
|
||
| - **Staging bottleneck**: Only one PR could be properly tested in the staging environment at a time, making parallel development difficult. | ||
| - **Unclear test failures**: When QA testing failed, it was hard to determine if the failure was from the specific PR being tested or from other changes that had been deployed to the shared staging environment. | ||
| - **Serial workflow**: Teams couldn't perform parallel quality assurance, forcing them to coordinate who could use staging at any given time. | ||
| - **Extra setup complexity**: During initial deployment, users had to create and configure a separate staging app with its own database, secrets, and resources. | ||
|
|
||
| Fly.io provides native support for PR preview environments through their `fly-pr-review-apps` GitHub Action, which can automatically create, update, and destroy ephemeral applications for each pull request. | ||
|
|
||
| This pattern is common in modern deployment workflows (Vercel, Netlify, Render, etc.) and provides isolated environments for testing changes before they reach production. | ||
|
|
||
| ## Decision | ||
|
|
||
| We've decided to replace the single shared staging environment with per-PR staging environments using Fly.io's PR review apps feature. Each pull request now: | ||
|
|
||
| - Gets its own isolated Fly.io application (e.g., `app-name-pr-123`) | ||
| - Automatically provisions all necessary resources (SQLite volume, Tigris object storage, Consul for LiteFS) | ||
| - Generates and stores secrets (SESSION_SECRET, HONEYPOT_SECRET) | ||
| - Seeds the database with test data for immediate usability | ||
| - Provides a direct URL to the deployed app in the GitHub PR interface | ||
| - Automatically cleans up all resources when the PR is closed | ||
|
|
||
| Staging environment secrets are now managed as GitHub environment secrets and passed to Fly in Github Actions. | ||
|
|
||
| The `dev` branch and its associated staging app have been removed from the deployment workflow. Production deployments continue to run only on pushes to the `main` branch. | ||
|
|
||
| ## Consequences | ||
|
|
||
| **Positive:** | ||
|
|
||
| - **Isolated testing**: Each PR has its own complete environment, making it clear which changes caused any issues | ||
| - **Simplified onboarding**: New users only need to set up one production app, not both production and staging | ||
| - **Better reviews**: Reviewers (including non-technical stakeholders) can click a link to see and interact with changes before merging | ||
| - **Automatic cleanup**: Resources are freed when PRs close, reducing infrastructure costs | ||
| - **Realistic testing**: Each PR tests the actual deployment process, catching deployment-specific issues early | ||
|
|
||
| **Negative:** | ||
|
|
||
| - **Increased resource usage during development**: Each open PR consumes Fly.io resources (though they're automatically cleaned up) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The deploy-staging job doesn't have appropriate dependencies on test/lint jobs. Unlike the production deployment which requires all checks to pass (lint, typecheck, vitest, playwright, container), the staging deployment will run regardless of test results. This could deploy broken code to staging environments.