Preview Deployments with Firebase Hosting & GitHub Actions…
    Neura MarketNeura Market/DeepSeek
    ChatGPTChatGPTClaudeClaudeGeminiGeminiCursorCursorGrokGrokPerplexityPerplexityDeepSeekDeepSeek
    CoPilotCoPilotStable DiffusionStable DiffusionMidjourneyMidjourney
    View All Directories
    OverviewRulesPromptsMCPsAgentsGamesBlogVideosGuidesCoursesCommunityTrending
    DeepSeekBlogPreview Deployments with Firebase Hosting & GitHub Actions
    Back to Blog
    Preview Deployments with Firebase Hosting & GitHub Actions
    firebase

    Preview Deployments with Firebase Hosting & GitHub Actions

    Ozan February 26, 2026
    0 views

    When I briefly worked with Peec AI, one of the first things I noticed that could greatly improve the...

    When I briefly worked with Peec AI, one of the first things I noticed that could greatly improve the developer experience was adding preview deployments to their toolbox. They already had code reviews, but the process felt incomplete without being able to click around a real, live version of the actual changes.

    This becomes even more critical in the age of vibe coding.

    Multiple screen control room

    Why Preview Deployments Matter

    If you've used Vercel or Netlify, you know this experience is built-in. Every pull request gets deployed to a separate environment, ideally with its own subdomain, so the result of the code changes can be showcased easily.

    For growing teams, the benefits compound quickly:

    • Visual verification — Catch UI bugs that diffs can never reveal
    • Faster feedback loops — Designers and PMs can review without setting up the project locally
    • AI agent workflows — When an agent like Cursor opens a PR, a human can visually verify the result before approving
    • Parallel development — Multiple PRs can be live simultaneously without stepping on each other
    • No staging bottleneck — Teams no longer wait for a single shared staging environment

    Firebase Hosting doesn't give you this out of the box, but with GitHub Actions and Firebase's Preview Channels feature, you can build the exact same thing.

    What We're Building

    Here's the full lifecycle we'll automate:

    PR lifecycle diagram: on PR open a preview channel is deployed and the URL is commented on the PR; on PR close the channel is deleted

    PR lifecycle diagram: on PR open a preview channel is deployed and the URL is commented on the PR; on PR close the channel is deleted

    1. A PR is opened or updated → a preview deployment is created (or updated)
    2. A bot comments the preview URL directly on the PR
    3. The PR is closed or merged → the preview deployment is automatically deleted

    Every commit to the PR branch refreshes the preview. No manual steps, no "can you deploy this to staging?"

    Prerequisites

    • A Firebase project with Hosting enabled
    • A GitHub repository
    • Firebase CLI installed (npm install -g firebase-tools)
    • A Firebase service account (we'll generate this)

    Implementation

    1. Setting Up Firebase Hosting

    If you haven't enabled Firebase Hosting yet, run:

    firebase init hosting
    

    Follow the prompts to set your public directory and configure as a single-page app if needed. Commit the generated firebase.json and .firebaserc to your repository.

    2. Generating a Service Account

    GitHub Actions needs permission to deploy to Firebase on your behalf. Go to your Firebase Console → Project Settings → Service Accounts → Generate new private key.

    Download the JSON file, then add its entire contents as a GitHub secret:

    Settings → Secrets and variables → Actions → New repository secret

    Name it FIREBASE_SERVICE_ACCOUNT.

    Note: Paste the raw JSON content as-is into the secret value. Do not base64-encode it or wrap it in quotes — the action reads it as a plain string and handles parsing internally.

    3. The Preview Deployment Workflow

    Create .github/workflows/preview.yml in your repository:

    name: Preview Deployment
    
    on:
      pull_request:
        types: [opened, synchronize, reopened]
    
    concurrency:
      group: firebase-preview-${{ github.event.pull_request.number }}
      cancel-in-progress: true
    
    permissions:
      contents: read
      pull-requests: write
    
    jobs:
      deploy_preview:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
    
          - uses: actions/setup-node@v4
            with:
              node-version: '20'
              cache: 'npm'
    
          - name: Install dependencies
            run: npm ci
    
          - name: Compute preview channel ID
            id: channel
            run: |
              PR_NUM="${{ github.event.pull_request.number }}"
              BRANCH="${{ github.event.pull_request.head.ref }}"
              BRANCH_20="${BRANCH:0:20}"
              RAW_ID="pr${PR_NUM}-${BRANCH_20}"
              CHANNEL_ID=$(echo "$RAW_ID" | sed 's/[^a-zA-Z0-9_.-]/_/g')
              echo "channel_id=$CHANNEL_ID" >> $GITHUB_OUTPUT
    
          - name: Build
            run: npm run build
    
          - name: Deploy to Firebase Preview Channel
            id: deploy
            uses: FirebaseExtended/action-hosting-deploy@v0
            with:
              firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
              channelId: ${{ steps.channel.outputs.channel_id }}
              expires: 7d
              disableComment: 'true'
    
          - name: Post or update PR comment with preview URL
            uses: actions/github-script@v7
            if: success() && github.event_name == 'pull_request'
            env:
              PREVIEW_URL: ${{ steps.deploy.outputs.details_url }}
            with:
              script: |
                const PREVIEW_MARKER = '<!-- firebase-hosting-preview -->';
                const previewUrl = process.env.PREVIEW_URL || '';
                const deployedAt = new Date().toISOString().replace('T', ' ').replace(/\.\d{3}Z$/, ' UTC');
                const body = `## Firebase Hosting Preview\n\n${PREVIEW_MARKER}\n\n**Preview:** [${previewUrl}](${previewUrl})\n\nLast deployed at **${deployedAt}**`;
    
                const { data: comments } = await github.rest.issues.listComments({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  issue_number: context.issue.number,
                });
                const botComment = comments.find(c => c.body && c.body.includes(PREVIEW_MARKER));
                if (botComment) {
                  await github.rest.issues.updateComment({
                    owner: context.repo.owner,
                    repo: context.repo.repo,
                    comment_id: botComment.id,
                    body,
                  });
                } else {
                  await github.rest.issues.createComment({
                    owner: context.repo.owner,
                    repo: context.repo.repo,
                    issue_number: context.issue.number,
                    body,
                  });
                }
    

    A few things to note:

    • The concurrency block cancels any in-progress deploy for the same PR when a new commit is pushed, preventing race conditions.
    • permissions: pull-requests: write is required for the comment step to work. Without it, you'll get a 403.
    • The channel ID is computed from both the PR number and branch name (e.g. pr42-fix-auth-bug), making channels readable in the Firebase console. Special characters in branch names are sanitized since Firebase rejects them.
    • disableComment: 'true' turns off the action's built-in comment so we can post a custom one via github-script . This gives full control over the comment format and uses a hidden HTML marker to find and edit the existing comment rather than posting a new one each time.
    • expires: 7d means the channel auto-expires after 7 days even if the cleanup workflow fails.

    GitHub bot comment on a pull request showing the Firebase preview deployment URL

    GitHub bot comment on a pull request showing the Firebase preview deployment URL

    4. Cleanup on PR Close

    When a PR is closed or merged, we want to remove the preview channel. Create .github/workflows/preview-cleanup.yml:

    name: Preview Cleanup
    
    on:
      pull_request:
        types: [closed]
    
    jobs:
      cleanup_preview:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
    
          - name: Compute preview channel ID
            id: channel
            run: |
              PR_NUM="${{ github.event.pull_request.number }}"
              BRANCH="${{ github.event.pull_request.head.ref }}"
              BRANCH_20="${BRANCH:0:20}"
              RAW_ID="pr${PR_NUM}-${BRANCH_20}"
              CHANNEL_ID=$(echo "$RAW_ID" | sed 's/[^a-zA-Z0-9_.-]/_/g')
              echo "channel_id=$CHANNEL_ID" >> $GITHUB_OUTPUT
    
          - name: Delete Firebase Preview Channel
            uses: FirebaseExtended/action-hosting-deploy@v0
            with:
              firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
              channelId: ${{ steps.channel.outputs.channel_id }}
              expires: 1d
    

    Setting expires: 1d is a belt-and-suspenders move, even if the action has any hiccups, the channel disappears within a day.

    Firebase Hosting console showing multiple preview channels

    Firebase Hosting console showing multiple preview channels

    Tips & Gotchas

    Include the branch name in the channel ID. Channel names must be unique per project. Using pr{number}-{branch} (e.g. pr42-fix-auth-bug) keeps them human-readable in the Firebase console and idempotent, the same PR always maps to the same channel. Just make sure to sanitize the branch name since Firebase rejects characters like / and :.

    Firebase Hosting has a channel limit. Free plans are capped at 7 preview channels per project. If your team runs many concurrent PRs, either upgrade to the Blaze plan or use a shorter expires window (e.g. 3d) to keep things from piling up. If you’re already on the paid plan,

    Cache your build. The biggest time cost is usually the build step, not the Firebase deploy itself. Using cache: 'npm' on the setup-node action shaves meaningful time off every run.

    Build environment variables. If your app needs environment variables at build time, expose them in the workflow's env: block. Preview deployments should point to a staging API, not production.

    The bot comment stays clean. The custom github-script step uses a hidden HTML marker (<!-- firebase-hosting-preview -->) to find and edit the existing comment on each push, rather than posting a new one every time. You get exactly one comment per PR with the always-current preview URL and timestamp.

    Conclusion

    Getting Firebase Hosting preview deployments working takes more legwork than Vercel or Netlify, but the end result is identical: every PR gets a live URL, every stakeholder can review without a local setup, and cleanup is automatic.

    For Peec AI, this changed how we do code reviews. Engineers spend less time on "can you deploy this so I can check?" and more time on the actual review. And as we lean more on AI agents to open PRs, having a visual verification step before merging has become non-negotiable.

    The two workflow files are concise enough to drop into any existing Firebase project in under 15 minutes. Give it a try or simply point your AI agent to this article and let it do the rest.

    Tags

    firebaseserverlesswebdev

    Comments

    More Blog

    View all
    Five Gemma-4 models, one accelerator: what porting E2B 31B to AWS Inferentia2 taught megemma

    Five Gemma-4 models, one accelerator: what porting E2B 31B to AWS Inferentia2 taught me

    I ported the whole Gemma-4 family — E2B, E4B, 12B, 31B, and the 26B-A4B MoE — to run on...

    X
    xbill
    Hey DEV, I'm Tobore. Let's actually connect.community

    Hey DEV, I'm Tobore. Let's actually connect.

    Hey DEV, I'm Tobore. Let's actually connect. I've been on here for a while now, mostly writing and...

    L
    Laurina Ayarah
    I burned through thousands of AI tokens. Then a friend did it for freeai

    I burned through thousands of AI tokens. Then a friend did it for free

    (yep, kinda clickbait, just for the funsies 😊) At the beginning of the year, I relaunched my...

    P
    Paulo Henrique
    Claude might be saturating your machineai

    Claude might be saturating your machine

    My laptop was sitting idle with the fan at full tilt. Nothing was running that I knew of. The culprit...

    S
    Sidhant Panda
    Automated GitHub Code Reviews Using Google Geminigithubactions

    Automated GitHub Code Reviews Using Google Gemini

    I Built a Thing! TL;DR — Google Gemini-based Pull Request reviews and Issue Triaging for...

    D
    Darren "Dazbo" Lester
    What is an "agentic harness," actually?ai

    What is an "agentic harness," actually?

    I've been hearing the word "harness" thrown around a lot lately. I assumed it just meant "the IDE" or...

    T
    Tilde A. Thurium

    Stay up to date

    Get the latest DeepSeek prompts, rules, and resources delivered to your inbox weekly.

    Neura Market LogoNeura Market

    Discover the best AI prompts, plugins, and resources for DeepSeek and more.

    Content Types

    • Rules
    • Prompts
    • MCPs
    • Agents
    • Guides

    Platforms

    • ChatGPT Directory
    • Claude Directory
    • Gemini Directory
    • Cursor Directory
    • Grok Directory
    • Perplexity Directory
    • DeepSeek Directory
    • CoPilot Directory
    • Stable Diffusion Directory
    • Midjourney Directory
    • All Directories

    Resources

    • Blog
    • Documentation
    • Help Center
    • Marketplace

    Legal

    • Privacy Policy
    • Terms of Service

    © 2026 Neura Market. All rights reserved.

    |

    Not affiliated with any AI platform vendors.

    Neura Market

    Custom AI Systems & Services

    Our team of experienced AI builders will help build custom AI systems, workflows, and solutions for your business.

    Request custom work

    Ready-made automations for this

    Workflows from the Neura Market marketplace related to this DeepSeek resource

    • Automate Blog Content Creation with Notion MCP, DeepSeek AI, and WordPressn8n · $9.99 · Related topic
    • Generate AI Videos from Scripts with DeepSeek, Synthesia, and Together.ain8n · $24.99 · Related topic
    • Compare Multi-Period Financial Data from Google Sheets with DeepSeek AI Analysisn8n · $14.99 · Related topic
    • Automatic Self-Hosted Application Updates with Coolify Deploymentsn8n · $14.99 · Related topic
    Browse all workflows