I hit a wall building a small RAG pipeline last week. I needed clean, consistent markdown from a dozen documentation sites, and every option felt wrong: paid scraping APIs meter you by the page, headless Chrome setups are heavy to babysit, and rolling my own fetcher meant writing a thousand lines of parsing glue that would break the moment a site changed its markup. Then I found Draco, a single-binary, native-Rust scraper that does what Firecrawl does without the browser fleet or the per-request boot time. I tested it end to end on my own machine, and honestly, it’s the closest thing I’ve seen to a “just works” self-hosted scraper. Here’s how to set it up and put it to work.

What Draco Actually Is
Draco is a web scraper written in Rust that points at a URL and returns clean markdown plus metadata. No Node, no headless Chrome fleet, no per-request browser boot. It uses a browser-faithful TLS and JA4 fingerprint to reach pages that block ordinary HTTP clients, then runs a deterministic main-content extraction pipeline that mirrors Firecrawl’s markdown output. The project is open source under MIT or Apache-2.0, and the entire tool ships as one binary.
The pitch is simple: you get the same “URL to markdown” workflow you’d pay Firecrawl for, but the scraper runs on your own hardware and your data never leaves your network. For anyone feeding documentation into an AI pipeline, that matters more than it sounds.
Why Self-Host a Scraper at All
I’ve been writing a lot about local-first AI tools like Collie, and self-hosted scraping is the same philosophy applied to data collection. The reasons are practical, not ideological:
- Cost. Paid scraping APIs bill per page, and documentation crawling burns through credits fast. A self-hosted scraper costs you electricity and bandwidth.
- Privacy. Every URL you scrape through a hosted API tells that vendor what you’re researching. Local scraping keeps your pipeline private.
- Rate limits. You control the throttle. No shared-tenant quota that mysteriously empties at noon.
- Latency. No round trip through someone else’s data center. On a typical page, Draco’s whole fetch-and-parse cycle ran in about 180 milliseconds in my tests.
There are trade-offs, of course. You’re responsible for keeping the tool updated and for respecting the sites you scrape. But for a small team or a solo developer, the control is worth it.
What You Need
Draco ships prebuilt binaries for Linux x86_64 and macOS arm64. Windows isn’t supported yet, which is fine if you’re on WSL like me. The binary is about 105 MB. No Node, no browser, no database — just the binary and an internet connection.
Step 1: Install Draco
The official install script detects your OS and architecture, pulls the latest release from GitHub, and adds the binary to your PATH. On Linux or macOS:
curl -fsSL https://raw.githubusercontent.com/0xchasercat/draco/main/install.sh | sh
The script drops the binary in ~/.draco/bin and appends it to your shell profile. You’ll need to reload your shell or run source ~/.bashrc before the draco command is available.
Prefer to do it by hand? The GitHub releases page has draco-linux-x86-64.tar.gz and draco-macos-arm64.tar.gz assets plus a SHA256SUMS file. Download, verify, extract, and you’re done.
One version note: the binary currently reports v0.19.0 when you run draco --version, even though the repository has moved past v0.20 tags. The tool itself is young and moving fast — check the changelog in the official repository before you build anything on it.
Step 2: The Glibc Gotcha (And the Docker Fix)
Here’s the thing that tripped me up, and it’s worth knowing before you hit it yourself. The prebuilt Linux binary is compiled against glibc 2.38, which ships with Ubuntu 24.04. On Ubuntu 22.04 — which is what my WSL environment runs — you’ll get a wall of GLIBC_2.38 not found errors the moment you try to run it.
You have two options. Update to a newer distro, or just run it in a container. Since Docker is available on most dev machines anyway, the container route is painless. I mounted the downloaded binary into an Ubuntu 24.04 image and it worked first try:
docker run --rm -v "$HOME/.draco/bin/draco:/usr/local/bin/draco" ubuntu:24.04 draco scrape https://example.com
That mounted binary approach is great for a quick test, but for a permanent setup you’ll want a small Dockerfile that copies the binary into a 24.04 base image. Either way, you sidestep the glibc mismatch completely.
Step 3: Scrape Your First Page
With Draco installed, scraping is one command:
draco scrape https://example.com
In my test that returned clean markdown in about 180 milliseconds:
# Example Domain
This domain is for use in documentation examples without needing permission. Avoid use in operations.
[Learn more](https://iana.org/domains/example)
Notice what’s missing: no nav bar, no footer, no script tags, no style noise. Draco strips the boilerplate and hands you the main content with links absolutized, code blocks fenced with their language, and GFM tables converted properly. I scraped the WireGuard quickstart and got a 6,000-character markdown document with every heading, command block, and link intact. I scraped a Wikipedia article and the infobox came through as a real markdown table.
Step 4: The Full JSON Envelope
Markdown on stdout is nice for piping, but most pipelines want structure. Add --json --pretty and Draco returns the full envelope:
draco scrape https://example.com --json --pretty
You get three things: the markdown body, a metadata block with title, language, canonical, favicon, every Open Graph and Twitter tag, plus source URL and status code, and a timing breakdown showing exactly where the milliseconds went:
{
"url": "https://example.com",
"status": "success",
"source_tier": "static",
"markdown": "# Example Domain ...",
"metadata": {
"title": "Example Domain",
"language": "en",
"sourceURL": "https://example.com",
"statusCode": 200,
"contentType": "text/html"
},
"timing": {
"network_ms": 21,
"parse_ms": 0,
"runtime_ms": 58,
"total_ms": 179
}
}
That source_tier field is worth understanding, because it’s what makes Draco different from a naive fetcher.
Step 5: How It Handles JavaScript-Heavy Sites
Modern sites are the problem every scraper has to solve. Some pages are thin HTML shells that only show content after JavaScript runs. Draco escalates through tiers: first the static HTML, then embedded state like __NEXT_DATA__ or JSON-LD, then a Next.js build-id replay that fetches the page’s own data JSON directly. If none of that works, it boots an in-process V8 isolate, lets the page’s JavaScript hydrate, and serializes the live DOM back to markdown.
That isolate deserves a closer look. It runs in a single-digit-millisecond restore time, it has no host bindings — page JavaScript can’t touch your filesystem or network directly — and every fetch it makes is brokered by Draco’s engine under a mutation-safety policy. It’s the same isolation class Puppeteer or jsdom rely on, just without the browser. In my tests, a thin shell like example.com escalated to the runtime tier automatically and the trace showed exactly which steps ran.
You can opt out of the heavier tiers with --tier-max 1 if you want speed over completeness, and exit code 3 tells you when a page genuinely needs a real browser. That’s honest behavior I appreciate.
Step 6: Run It as a Server
This is the part that sold me. Draco has a daemon mode that exposes a Firecrawl-compatible REST API, so existing Firecrawl clients can point at your local instance without code changes:
draco serve
# listens on http://127.0.0.1:3002 — Firecrawl's default port
The daemon stays warm, so there’s no per-request binary spawn. A health check confirms it’s alive:
curl http://127.0.0.1:3002/health
# {"status":"ok","version":"0.19.0","availableSlots":8,...}
Then the scrape endpoint behaves exactly like Firecrawl’s:
curl -X POST http://127.0.0.1:3002/v1/scrape \
-H "content-type: application/json" \
-d '{"url": "https://example.com", "formats": ["markdown"]}'
The response uses the Firecrawl envelope — {"success": true, "data": {"markdown": "...", "metadata": {...}}} — plus a draco object with the source tier, timing, and execution trace. In my run, the whole round trip took 160 milliseconds.
Beyond scraping, the daemon speaks the rest of the Firecrawl API. POST /v1/map discovers a site’s URLs by merging its sitemap with on-page links — I pointed it at wireguard.com and got back ten same-host URLs in one call. POST /v1/crawl runs bounded site crawls as async jobs you poll. POST /v1/batch/scrape scrapes a list of URLs in parallel. There’s even webhook support so long jobs can notify your app when they finish.
Step 7: Extra Powers — JSON Extraction, Search, and MCP
Draco isn’t just a markdown machine. A few commands are worth knowing about:
--format jsonextracts the structured data a SPA loads from its own API — the cheapest tier that yields data wins, so you don’t scrape HTML at all when the site has an API underneath.--format endpointssurfaces the full ranked catalog of APIs a page calls, with a replayable winner. Great for reverse-engineering how a site loads data.draco searchruns metasearch across several engines over plain HTTP, merging results by consensus so captcha walls on one engine don’t kill your query.draco mcpexposes scraping as Model Context Protocol tools, so agent clients like Claude or your editor can call Draco directly. That’s a genuinely useful bridge between scraping and the AI agent workflows everyone is building right now.
Stealth and Politeness: The Fine Print
Draco’s defaults are polite: it respects robots.txt, applies per-host rate limiting, and keeps retries bounded. The JA4 fingerprint emulation exists for compatibility with sites that block ordinary clients — it’s not a tool for bypassing authentication or access controls. The README is explicit about this, and you should be too: only scrape public data, sites you operate, or APIs you’re permitted to use. If a page throws a genuine JavaScript challenge wall like Cloudflare or DataDome, Draco doesn’t pretend to win — it short-circuits to a needs_browser result. That honesty is refreshing in a tool space full of overpromises.
Before you adopt any open-source tool, it’s worth running the same hygiene checks I use: verify the maintainer’s history, read the source for anything suspicious, and pin the version you deploy. I covered the full workflow in my supply chain security guide — the same principles apply to a single-binary scraper as to an npm package.
When Draco Isn’t the Answer
Be honest about the boundaries. If your target pages sit behind aggressive bot management, you’ll still need a real browser. If you need screenshots or PDF rendering, Draco doesn’t produce those yet — the daemon rejects unsupported formats with a clear 400 instead of silently faking them. And if your crawl volume is enormous, a single warm process will still be bounded by your own network and machine. For documentation pipelines, research tooling, and RAG data collection, though, it covers the overwhelming majority of cases.
Bottom Line
Draco scratches a very specific itch: clean markdown from the web, self-hosted, without a browser fleet. It’s young — the version string lags the release tags, and the API is still settling — so treat it as a powerful tool with sharp edges, not a finished platform. But the fundamentals are right: a single binary, honest output, a Firecrawl-compatible API, and a sandboxed runtime for JavaScript-heavy pages. Whether you’re building a documentation index, feeding a local LLM, or just tired of watching API credits drain, it’s worth an afternoon of your time. My spider web of crawlers just got a lot cheaper to weave.