🔄 Git Workflow: Staging → Production
Learn the proper CI/CD workflow for safely deploying features from staging to production without breaking things for real users.
Git Workflow: Staging → Production
Learn the proper CI/CD workflow for safely deploying features from development to production without breaking things for real users.
🎯 Why This Matters
Once you have paying customers, deploying code becomes high stakes. One bad deployment can:
- Break payment processing (lost revenue)
- Corrupt user data (angry customers)
- Take your site offline (reputation damage)
This guide shows you the battle-tested workflow that prevents these disasters.
🔄 The Golden Rule
NEVER commit directly to main branch after you have real users.
Always follow this flow:
Local Development → staging branch → Test → main branch → Production
📊 Two Phases: Pre-Launch vs Post-Launch
Phase 1: Pre-Launch (No Real Users Yet)
While you're still building and testing with fake data, the workflow is relaxed:
# Work on staging branch
git checkout staging
git add .
git commit -m "Add new feature"
git push origin staging
# Deploy staging subdomain (staging.yourstartup.ai)
# Test the feature
# If good, merge to main
git checkout main
git merge staging
git push origin main
# Deploy production (yourstartup.ai)
Why it's okay to be relaxed: No real users means no consequences if something breaks. But get into the habit now - it'll save you pain later.
Phase 2: Post-Launch (Real Users & Revenue)
Once you have paying customers, strict workflow required:
# 1. Create feature branch from staging
git checkout staging
git pull origin staging
git checkout -b feature/new-pricing-table
# 2. Develop & commit on feature branch
git add .
git commit -m "Update pricing table layout"
# 3. Merge to staging
git checkout staging
git merge feature/new-pricing-table
git push origin staging
# 4. Deploy staging subdomain
# (Your CI/CD should auto-deploy staging.yourstartup.ai)
# 5. TEST THOROUGHLY on staging
# - Try all features
# - Test with real-ish data
# - Check mobile/desktop
# - Run through user flows
# - Use Stripe test mode for payments
# 6. ONLY after testing passes → merge to main
git checkout main
git merge staging
git push origin main
# 7. Deploy production
# (Your CI/CD deploys yourstartup.ai)
# 8. Monitor for 30 minutes
# - Check error logs
# - Monitor payment webhooks
# - Watch user activity
🏗️ Branch Strategy
Main Branch (main)
- Purpose: Production-ready code, ALWAYS stable
- Deploys to:
yourstartup.ai(production) - Rule: Only receives tested code from
staging - Protected: Should require pull request review (if team)
Staging Branch (staging)
- Purpose: Pre-production testing environment
- Deploys to:
staging.yourstartup.ai(or test subdomain) - State: Mostly stable, may have minor bugs
- Safe to: Break things, test aggressively
Feature Branches (feature/*)
- Purpose: Individual features or fixes
- Examples:
feature/new-dashboard,fix/payment-bug,refactor/email-system - Lifecycle: Created from
staging, merged back tostaging, then deleted - Rule: Never merge feature branches directly to
main
✅ What to Test on Staging
Before merging staging → main, verify:
Functional Testing
- ✅ New feature works as expected
- ✅ Existing features still work (regression testing)
- ✅ All user flows complete successfully
- ✅ Forms submit correctly
- ✅ Error handling works
Payment Testing (Critical!)
- ✅ Stripe test mode payments process
- ✅ Webhooks trigger correctly
- ✅ Invoices generate
- ✅ User plan updates after payment
- ✅ Email confirmations send
Database Testing
- ✅ Migrations run without errors
- ✅ No data loss or corruption
- ✅ Queries perform well (not too slow)
- ✅ Relationships/constraints intact
UI/UX Testing
- ✅ Desktop layout looks correct
- ✅ Mobile responsive works
- ✅ Dark mode (if applicable)
- ✅ No broken images or 404s
- ✅ Loading states work
Integration Testing
- ✅ External APIs respond (Stripe, SendGrid, etc.)
- ✅ OAuth login works
- ✅ Email sending works
- ✅ File uploads work
🚨 Rules to NEVER Break
❌ NEVER Do These (Post-Launch):
-
Never deploy to production on Friday afternoon
- If something breaks, you're working all weekend
- Deploy Monday-Wednesday mornings instead
-
Never skip testing on staging
- "It's a small change" - famous last words before disaster
- ALWAYS test, even one-line changes can break things
-
Never test real payments on production
- Use Stripe test mode on staging
- Real payment testing = real charges to real customers
-
Never run untested database migrations on production
- Migrations can lock tables, corrupt data, or fail halfway
- Test on staging database first
-
Never deploy during high-traffic hours
- Deploy during low-traffic times (early morning)
- Fewer users affected if something goes wrong
-
Never deploy multiple big features at once
- If something breaks, you can't tell which feature caused it
- Deploy incrementally, one feature at a time
✅ ALWAYS Do These:
-
Always have database backups before migrations
- Automated daily backups
- Manual backup before big migrations
- Test restore process periodically
-
Always monitor logs after deployment
- Watch for 30 minutes after deploying
- Check error rates, response times
- Monitor Stripe webhook logs
-
Always have a rollback plan
- Know how to revert to previous version quickly
- Keep previous Docker image / deployment ready
- Document rollback steps in runbook
-
Always use environment variables
- Never hardcode API keys
- Different keys for staging vs production
- Use
.env.local(gitignored)
-
Always version your API
- API v1, v2, etc.
- Don't break existing integrations
- Deprecate gradually, not overnight
🛠️ Recommended Setup
Separate Environments
Production:
- Domain: yourstartup.ai
- Database: yourstartup_prod
- Stripe: Live keys (sk_live_...)
- Branch: main
Staging:
- Domain: staging.yourstartup.ai
- Database: yourstartup_staging
- Stripe: Test keys (sk_test_...)
- Branch: staging
Environment Variables
Production (.env.production):
DATABASE_URL=postgres://prod-db-url
STRIPE_SECRET_KEY=sk_live_xxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxx
NEXT_PUBLIC_BASE_URL=https://yourstartup.ai
NODE_ENV=production
Staging (.env.staging):
DATABASE_URL=postgres://staging-db-url
STRIPE_SECRET_KEY=sk_test_xxxxx
STRIPE_WEBHOOK_SECRET=whsec_test_xxxxx
NEXT_PUBLIC_BASE_URL=https://staging.yourstartup.ai
NODE_ENV=development
CI/CD Automation (Vercel Example)
Vercel automatically deploys based on branch:
# vercel.json (optional custom config)
{
"git": {
"deploymentEnabled": {
"main": true,
"staging": true
}
},
"github": {
"autoAlias": true
}
}
How it works:
- Push to
staging→ Auto-deploys tostaging.yourstartup.ai - Push to
main→ Auto-deploys toyourstartup.ai - Feature branches → Deploy to preview URLs (optional)
📖 Real-World Example Workflow
Let's say you want to add a new pricing tier:
Step 1: Create Feature Branch
git checkout staging
git pull origin staging
git checkout -b feature/enterprise-tier
Step 2: Develop the Feature
# Make changes to pricing page, database schema, checkout flow
git add .
git commit -m "Add Enterprise tier pricing ($2,997/mo)"
Step 3: Merge to Staging
git checkout staging
git merge feature/enterprise-tier
git push origin staging
Step 4: Deploy Staging
# If using Vercel/Netlify, this happens automatically
# Otherwise: SSH into staging server and pull latest
ssh staging-server
cd /var/www/yourstartup
git pull origin staging
pm2 restart app
Step 5: Test on Staging
Visit staging.yourstartup.ai:
- ✅ New Enterprise tier shows on pricing page
- ✅ Click "Get Started" → Stripe checkout loads
- ✅ Use test card
4242 4242 4242 4242 - ✅ Payment succeeds, user upgraded to Enterprise
- ✅ Confirmation email sends
- ✅ Dashboard shows Enterprise features unlocked
Step 6: Test Edge Cases
- ❌ Try invalid card → Error handled gracefully
- ❌ Try expired card → Shows proper message
- ✅ Cancel checkout → Returns to pricing page
- ✅ Refresh page mid-checkout → Session persists
- ✅ Try on mobile → Layout looks good
Step 7: Check Database
SELECT * FROM users WHERE plan = 'ENTERPRISE';
-- Verify user record updated correctly
SELECT * FROM orders WHERE plan = 'ENTERPRISE';
-- Verify order created with correct amount
Step 8: If All Tests Pass → Merge to Main
git checkout main
git pull origin main
git merge staging
git push origin main
Step 9: Deploy Production
# Automatic if using CI/CD
# Or manually:
ssh production-server
cd /var/www/yourstartup
git pull origin main
npm run build
pm2 restart app
Step 10: Monitor Production
For the next 30 minutes, watch:
- Error logs:
pm2 logsor Vercel dashboard - Stripe dashboard: Check webhook deliveries
- Database: Monitor for unusual activity
- User feedback: Check support tickets
Step 11: Clean Up
# Delete feature branch (no longer needed)
git branch -d feature/enterprise-tier
git push origin --delete feature/enterprise-tier
🆘 When Things Go Wrong
Scenario 1: Bug Found After Production Deploy
DO:
- Assess severity (site down? or minor visual bug?)
- If critical: Rollback immediately
- If minor: Fix on
staging, test, then deploy fix
Rollback:
# Option A: Revert the commit
git checkout main
git revert HEAD # Creates new commit that undoes last commit
git push origin main
# Option B: Hard reset (dangerous!)
git reset --hard HEAD~1 # Go back 1 commit
git push --force origin main # WARNING: Only if no team members pulled yet
Scenario 2: Database Migration Fails
DO:
- Check error logs
- If migration partially applied: Manually fix database state
- Write rollback migration
- Test rollback on staging
- Apply rollback to production if needed
Prevention:
- Always make migrations reversible
- Test on staging database with production-like data size
- Run migrations during low-traffic hours
- Have database backup before migration
Scenario 3: Payment Processing Broken
CRITICAL - FIX IMMEDIATELY:
- Check Stripe dashboard: Webhooks failing?
- Check logs: API errors?
- If site is processing payments but webhook broken: Manually fulfill orders later
- If checkout completely broken: Rollback NOW
📚 Additional Resources
Git Commands Cheat Sheet
# See current branch
git branch --show-current
# See all branches
git branch -a
# Switch branches
git checkout staging
# Create new branch
git checkout -b feature/new-thing
# See uncommitted changes
git status
# See commit history
git log --oneline --graph
# Undo last commit (keep changes)
git reset --soft HEAD~1
# Undo last commit (discard changes)
git reset --hard HEAD~1
# Merge branch
git checkout staging
git merge feature/new-thing
# Delete local branch
git branch -d feature/new-thing
# Delete remote branch
git push origin --delete feature/new-thing
Useful Aliases
Add to ~/.gitconfig:
[alias]
st = status
co = checkout
br = branch
ci = commit
unstage = reset HEAD --
last = log -1 HEAD
visual = log --oneline --graph --all
Now you can use:
git st # Instead of git status
git co main # Instead of git checkout main
git visual # See commit graph
✅ Checklist: Ready to Deploy?
Before merging staging → main:
- [ ] All tests pass on staging
- [ ] Feature works on desktop
- [ ] Feature works on mobile
- [ ] Edge cases handled (errors, invalid input)
- [ ] Database migrations tested
- [ ] No console errors in browser
- [ ] Lighthouse score acceptable (if applicable)
- [ ] Stripe test payments work
- [ ] Emails send correctly
- [ ] No broken links or 404s
- [ ] Rollback plan documented
- [ ] Team notified (if applicable)
- [ ] Deploy scheduled for low-traffic time
- [ ] Monitoring tools ready
🎓 Key Takeaways
- Staging is your safety net - Test everything there first
- Main = Production - Only stable, tested code
- Feature branches keep work isolated - Multiple features can be in progress
- Test before merging - Every. Single. Time.
- Monitor after deploying - Catch issues early
- Have a rollback plan - Things will go wrong eventually
- Deploy incrementally - Small changes = easier debugging
- Protect user data - It's your most valuable asset
💬 Questions?
If you run into issues with your deployment workflow, check:
Or reach out to support with your deployment logs.
Pro Tip: Print this workflow and tape it near your monitor. Following it religiously will save you from the nightmare of debugging production issues at 2am while angry customers flood your support inbox. 😅
Happy deploying! 🚀
Was this article helpful?
Let us know so we can improve our docs