Preview Deployments with Firebase Hosting & GitHub Actions — DeepSeek Blog | Neura Market
    Neura MarketNeura Market/DeepSeek
    ChatGPTChatGPTClaudeClaudeGeminiGeminiCursorCursorGrokGrokPerplexityPerplexityDeepSeekDeepSeek
    CoPilotCoPilotStable DiffusionStable DiffusionMidjourneyMidjourney
    View All Directories
    OverviewRulesPromptsMCPsAgentsBlogVideosGuidesCoursesCommunityTrendingGenerate
    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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g9coe03uwqok4usknk3r.jpg) ## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f7rjgahkw4omv9uqg2d7.png) 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: ```bash 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: ```yaml 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/as8vk8ibs0fitwordgo7.png) 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`: ```yaml 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ivomspt5jjy7jkurb6us.png) 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
    How I'm using ASTs and Gemini to solve the "Codebase Onboarding" problem 🧠ai

    How I'm using ASTs and Gemini to solve the "Codebase Onboarding" problem 🧠

    Hi everyone! 👋 I’m Tara, a Senior Software Engineer and Consultant. Over the years, I've jumped...

    T
    tworrell
    Local AI Will Save Us All (The Math Says So, Trust Me)ai

    Local AI Will Save Us All (The Math Says So, Trust Me)

    Every few weeks a take goes viral in tech circles making the case for ditching cloud AI and running...

    S
    Sebastian Schürmann
    Lost in the AI Hype, I Started Smallai

    Lost in the AI Hype, I Started Small

    And it helped me get back into tech without drowning TL;DR at the end Coming back to...

    R
    Rohini Gaonkar
    Building a Replay-Tested Interactive Brokers Client in Gogo

    Building a Replay-Tested Interactive Brokers Client in Go

    I wanted an IBKR library that felt like Go and had testing I could trust. So I wrote one.

    T
    Thomas Marcelis
    Playwright in Pictures: Fully Parallel Modeplaywright

    Playwright in Pictures: Fully Parallel Mode

    Playwright’s fullyParallel mode is often treated as a simple performance switch. In practice, it...

    V
    Vitaliy Potapov
    Designing a CLI for Both Humans and Agentscli

    Designing a CLI for Both Humans and Agents

    Learn how Alpic designed its CLI for both human developers and AI agents — covering tradeoffs like polling, context windows, interactivity, and statelessness.

    J
    Julien Vallini

    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.