How PreviewDrop Deploys a Branch in About a Minute
Every engineering team that isn't shipping a Next.js frontend has the same problem: designers, PMs, and QA want a live URL on every branch, and you don't have one. The workflow Vercel made standard in 2019 never reached Django shops, Rails teams, Expo projects, or Go monoliths.
We built PreviewDrop to fill that gap. Here's how we made "push a branch, get a live URL" take about a minute for many apps — across any framework we can detect.
The pipeline most teams build by hand
Before we get to our architecture, let's look at what most teams do today. The standard DIY pipeline looks like this:
# .github/workflows/preview.yml — 200+ lines of YAML
name: Preview Deploy
on:
push:
branches-ignore: [main]
jobs:
deploy-preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t myapp:${{ github.sha }} .
- name: Push to registry
run: |
docker tag myapp:${{ github.sha }} registry.example.com/myapp:${{ github.sha }}
docker push registry.example.com/myapp:${{ github.sha }}
- name: SSH deploy
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.STAGING_HOST }}
script: |
docker pull registry.example.com/myapp:${{ github.sha }}
docker stop myapp-preview || true
docker rm myapp-preview || true
docker run -d --name myapp-preview \
-p 3000:3000 \
--label traefik.enable=true \
--label traefik.http.routers.myapp.rule=Host(\`${{ github.sha }}.preview.example.com\`) \
registry.example.com/myapp:${{ github.sha }}
This pipeline typically takes 4–8 minutes. It breaks when the registry auth token rotates. It requires every engineer to understand Docker networking. And there's no PR comment, no password protection, no auto-expiry, no live build logs.
How PreviewDrop does it
Our pipeline replaces those 200 lines of YAML with zero configuration. Here's the sequence from webhook to live URL:
- GitHub App receives the push event — branch push or PR open
- Framework detection — PreviewDrop inspects the repo and generates a tuned build plan
- Build cache — warm redeploys skip layers that haven't changed
- Dynamic routing picks up the running container — no DNS propagation delay
- Stability poll for 12 seconds — confirms the app is responding before marking it ready
- PR comment posted — URL lands in the PR thread automatically
The key insight: the router watches the container runtime directly. The moment the container starts with the right labels, the URL is routable. There's no DNS TTL to wait for, no load balancer registration delay. Container-start to live-URL is measured in single-digit seconds.
Where the seconds actually go
A deploy spends its time in the same stages every time: a shallow git clone, framework detection, the image build, container start, and a stability poll before the URL is marked ready. The build dominates, so the total depends on your stack. Measured from submit to live URL in September 2026:
small Express app .......... ~29s
one of our sample repos .... ~81s
FastAPI app ................ ~101s
Warm redeploys are faster because the build cache reuses cached layers. The generated build is deterministic — same package.json, same Gemfile, same requirements.txt produce the same layer hashes. After the first deploy on a branch, every subsequent push only rebuilds the application code layer.
Cold deploys (first push on a new branch, or a new stack) are slower — 90 seconds to 4 minutes depending on the framework. A Rails app with native extensions takes longer than a Go binary. We don't hide this; every engineer reading this is already hunting for the asterisk, and honesty earns the credibility you need on Hacker News.
The CLI: one command, any detected stack
If you prefer running deploys from the terminal instead of relying on GitHub webhooks, the PreviewDrop CLI handles everything with a single command:
npx previewdrop deploy --repo . --branch feat/new-dashboard
The CLI detects your framework, packages the source, submits it to the build queue, and streams live build logs back to your terminal over WebSocket. When the preview is ready, it prints the URL and copies it to your clipboard.
This is especially useful during local development — you can push a preview without pushing to GitHub, or trigger a redeploy when you've already pushed and just want to see the result faster.
What we sacrificed
No architecture post is honest without the trade-offs. Here's what we chose to leave out:
- Single-instance worker. The build queue runs on one host with an in-memory queue. This caps us at roughly 14 concurrent previews on a CPX32 or 29 on a CPX41. If you're running 100+ PRs a day, this architecture isn't for you — yet. We'll move to Redis-backed queues and distributed workers when the ceiling becomes real.
- Host-local build cache. The layer cache lives on the worker's disk. Cache misses on a second worker are cold. A shared distributed cache layer is on the roadmap.
- No Kubernetes. Deliberately. Teams that need K8s-native previews already have Uffizzi, Okteto, or Porter. We're for the teams that don't want to manage a cluster just to see what their PR looks like.
Try it on your stack
PreviewDrop works with any framework it can detect: Expo web, Rails, Django, Laravel, FastAPI, Spring Boot, Go, Rust, Next.js, Express, and anything with a package.json, Gemfile, requirements.txt, go.mod, or Cargo.toml.
Start free with GitHub — no credit card, two concurrent previews on the free tier. Push a branch, get a live URL, share it with your team. No Dockerfile needed — the stack is detected and built for you.
See also: Expo web preview in 2 minutes · Cloud bill tripled? Flat vs usage-based pricing · Docs — Quickstart
Want previews on your own pull requests? Start free — 2 concurrent previews, no credit card.