-
Notifications
You must be signed in to change notification settings - Fork 463
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
Conversation
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.
Pull request overview
This PR transitions from a shared staging environment deployed from a dev branch to per-PR staging environments using Fly.io's PR review apps feature. Each pull request now gets its own isolated Fly.io application with automatically provisioned resources, seeded test data, and automatic cleanup when the PR is closed.
Key Changes:
- Implements per-PR staging environments in GitHub Actions workflow with automatic resource provisioning and cleanup
- Removes staging environment setup from the Remix initialization script
- Updates all documentation to reflect the new deployment model where staging uses GitHub environment secrets
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| .github/workflows/deploy.yml | Adds new deploy-staging job for per-PR environments with automatic app creation and cleanup; updates production deployment conditions |
| remix.init/index.mjs | Removes staging environment setup prompts and configuration |
| prisma/seed.staging.sql | New SQL seed file with test data for staging environments |
| other/litefs.yml | Adds automatic seeding of staging databases based on APP_ENV variable |
| fly.toml | Updates app name and region (appears to be environment-specific) |
| docs/deployment.md | Simplifies deployment instructions to only require production app setup |
| docs/secrets.md | Documents how to manage secrets for both production (Fly) and staging (GitHub) |
| docs/monitoring.md | Updates Sentry setup instructions for new staging approach |
| docs/email.md | Updates Resend API key setup instructions for new staging approach |
| docs/database.md | Documents staging database seeding approach |
| docs/decisions/047-pr-staging-environments.md | Architectural decision record explaining the change |
| app/routes/users/index.tsx | Adds "Test" suffix to page heading (appears accidental) |
| prisma/migrations/migration_lock.toml | Reformatting only |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| flyctl deploy . \ | ||
| --ha=false \ # use only one machine for staging |
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 \ |
| uses: superfly/flyctl-actions/[email protected] | ||
|
|
||
| - name: 🚁️ Deploy PR app to Fly.io | ||
| if: ${{ github.event.action != 'closed' && env.FLY_API_TOKEN }} |
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 condition only checks if FLY_API_TOKEN exists but doesn't verify that this is a pull request event. This could cause issues if the action type is something other than 'closed' but FLY_API_TOKEN is set. Consider adding a check for github.event_name == 'pull_request' as well.
|
|
||
| > **Note**: Make sure this name matches the `app` set in your `fly.toml` | ||
| > file. Otherwise, you will not be able to deploy. | ||
| 1. Change the app name in fly.toml to name of the app you just created. |
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 instructions say to change the app name in fly.toml to match the app created, but this contradicts the earlier note in line 41 which says "Change the app name in fly.toml to name of the app you just created." The workflow uses the app name from fly.toml to generate PR app names, so the instruction should also mention that the app name will be used as a prefix for PR environments (e.g., app-name-pr-123).
| 1. Change the app name in fly.toml to name of the app you just created. | |
| 1. Change the `app` name in `fly.toml` to match the Fly app you just created. This | |
| name will also be used as a prefix for preview (PR) environments (for example, | |
| `your-app-name-pr-123`). |
| 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 }} | ||
| flyctl consul attach --app $FLY_APP_NAME | ||
| # Don't log the created tigris secrets! | ||
| flyctl storage create --app $FLY_APP_NAME --name epic-stack-$FLY_APP_NAME --yes > /dev/null 2>&1 |
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 |
| run: | | ||
| FLY_APP_NAME="${{ steps.app_name.outputs.value }}-pr-${{ github.event.number }}" | ||
| flyctl storage destroy epic-stack-$FLY_APP_NAME --yes || true | ||
| flyctl apps destroy "$app" -y || true |
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 |
| - 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 | ||
| flyctl apps create $FLY_APP_NAME --org $FLY_ORG | ||
| 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 }} | ||
| flyctl consul attach --app $FLY_APP_NAME | ||
| # Don't log the created tigris secrets! | ||
| flyctl storage create --app $FLY_APP_NAME --name epic-stack-$FLY_APP_NAME --yes > /dev/null 2>&1 | ||
| fi | ||
| flyctl deploy . \ | ||
| --ha=false \ # use only one machine for staging | ||
| --regions $FLY_REGION \ | ||
| --vm-size shared-cpu-1x \ | ||
| --env APP_ENV=staging \ | ||
| --env ALLOW_INDEXING=false \ | ||
| --app $FLY_APP_NAME \ | ||
| --image-label ${{ github.sha }} \ | ||
| --build-arg COMMIT_SHA=${{ github.sha }} \ | ||
| --build-secret SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} \ | ||
| --app ${{ steps.app_name.outputs.value }} | ||
| env: | ||
| FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} | ||
| --build-secret SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} |
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.
| await $I`fly secrets set SESSION_SECRET=${getRandomString32()} INTERNAL_COMMAND_TOKEN=${getRandomString32()} HONEYPOT_SECRET=${getRandomString32()} ALLOW_INDEXING=false --app ${APP_NAME}-staging` | ||
| } | ||
| await $I`fly secrets set SESSION_SECRET=${getRandomString32()} INTERNAL_COMMAND_TOKEN=${getRandomString32()} HONEYPOT_SECRET=${getRandomString32()} --app ${APP_NAME}` | ||
| await $I`fly secrets set SESSION_SECRET=${getRandomString32()} HONEYPOT_SECRET=${getRandomString32()} --app ${APP_NAME}` |
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 INTERNAL_COMMAND_TOKEN secret has been removed from the fly secrets setup, but it's still required in the codebase. The env.server.ts file expects INTERNAL_COMMAND_TOKEN to be defined (line 8), and it's used in app/routes/admin/cache/sqlite.server.ts for authentication between LiteFS instances. This will cause the application to fail at startup with an "Invalid environment variables" error.
| await $I`fly secrets set SESSION_SECRET=${getRandomString32()} HONEYPOT_SECRET=${getRandomString32()} --app ${APP_NAME}` | |
| await $I`fly secrets set SESSION_SECRET=${getRandomString32()} HONEYPOT_SECRET=${getRandomString32()} INTERNAL_COMMAND_TOKEN=${getRandomString32()} --app ${APP_NAME}` |
| secrets, this is to prevent duplicate content from being indexed multiple | ||
| times by search engines. To do this you can run the following commands: | ||
| ```sh | ||
| gh secret set FLY_API_TOKEN --body "<token>" |
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 commands shown use the GitHub CLI without specifying the repository. While this may work if run from within the repository directory, it would be clearer and more robust to include the repository context. Consider adding a note that these commands should be run from within the repository directory, or show how to specify the repository explicitly.
| gh secret set FLY_API_TOKEN --body "<token>" | |
| gh secret set FLY_API_TOKEN --body "<token>" --repo <GITHUB_OWNER>/<GITHUB_REPO> |
| app = "epic-rsc-stack" | ||
| primary_region = "ams" |
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 app name and primary_region have been changed from "epic-stack-template" in "sjc" to "epic-rsc-stack" in "ams". This appears to be specific to a particular deployment rather than a template configuration. These values should typically remain as placeholders or be set during the initialization process, not committed with specific values.
| app = "epic-rsc-stack" | |
| primary_region = "ams" | |
| app = "epic-stack-template" | |
| primary_region = "sjc" |
| # Don't log the created tigris secrets! | ||
| flyctl storage create --app $FLY_APP_NAME --name epic-stack-$FLY_APP_NAME --yes > /dev/null 2>&1 | ||
| 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.
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 |
Test Plan
Checklist
Screenshots