'Configure Firecrawl local development with self-hosted Docker, mocking,
Scanned 9/2/2026
Install to Claude Code
npx -y skills add jeremylongshore/tons-of-skills-marketplace --skill firecrawl-local-dev-loop --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Firecrawl Local Dev Loop?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/jeremylongshore-firecrawl-local-dev-loop-tons-of-skills-marketplace)More formats (shields.io, HTML) on the badges page.
---
name: firecrawl-local-dev-loop
description: 'Configure Firecrawl local development with self-hosted Docker, mocking,
and testing.
Use when setting up a development environment, running Firecrawl locally to save
credits,
or configuring test workflows with vitest.
Trigger with phrases like "firecrawl dev setup", "firecrawl local development",
"firecrawl docker", "firecrawl self-hosted dev", "firecrawl test setup".
'
allowed-tools: Read, Write, Edit, Bash(npm:*), Bash(pnpm:*), Bash(docker:*), Grep
version: 1.11.0
license: MIT
author: Jeremy Longshore <jeremy@intentsolutions.io>
tags:
- saas
- firecrawl
- testing
- workflow
compatibility: Designed for Claude Code
---
# Firecrawl Local Dev Loop
## Overview
Set up a fast development workflow for Firecrawl integrations. Use self-hosted Firecrawl via Docker to avoid burning API credits during development, mock the SDK for unit tests, and run integration tests against the local instance.
## Prerequisites
- Node.js 18+ with npm/pnpm
- Docker + Docker Compose (for self-hosted Firecrawl)
- `@mendable/firecrawl-js` installed
## Instructions
### Step 1: Project Structure
```
my-firecrawl-project/
├── src/
│ ├── scraper.ts # Firecrawl business logic
│ └── config.ts # Environment-aware config
├── tests/
│ ├── scraper.test.ts # Unit tests (mocked SDK)
│ └── integration.test.ts # Integration tests (real API)
├── docker-compose.yml # Self-hosted Firecrawl
├── .env.local # Dev secrets (git-ignored)
├── .env.example # Template for team
└── package.json
```
### Step 2: Self-Hosted Firecrawl for Zero-Credit Dev
```yaml
# docker-compose.yml
services:
firecrawl:
image: mendableai/firecrawl:latest
ports:
- "3002:3002"
environment:
- PORT=3002
- USE_DB_AUTHENTICATION=false
- REDIS_URL=redis://redis:6379
- NUM_WORKERS_PER_QUEUE=1
- BULL_AUTH_KEY=devonly
depends_on:
redis:
condition: service_healthy
redis:
image: redis:7-alpine
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
```
```bash
set -euo pipefail
# Start local Firecrawl
docker compose up -d
# Verify it's running
curl -s http://localhost:3002/health | jq .
```
### Step 3: Environment-Aware Configuration
```typescript
// src/config.ts
import FirecrawlApp from "@mendable/firecrawl-js";
export function getFirecrawl(): FirecrawlApp {
const isDev = process.env.NODE_ENV !== "production";
return new FirecrawlApp({
apiKey: process.env.FIRECRAWL_API_KEY || "fc-dev",
// Point to local Docker instance in dev
...(isDev && process.env.FIRECRAWL_API_URL
? { apiUrl: process.env.FIRECRAWL_API_URL }
: {}),
});
}
```
```bash
# .env.local (for development — zero API credits used)
FIRECRAWL_API_KEY=fc-localdev
FIRECRAWL_API_URL=http://localhost:3002
NODE_ENV=development
```
### Step 4: Unit Tests with Mocked SDK
```typescript
// tests/scraper.test.ts
import { describe, it, expect, vi, beforeEach } from "vitest";
// Mock the SDK
vi.mock("@mendable/firecrawl-js", () => ({
default: vi.fn().mockImplementation(() => ({
scrapeUrl: vi.fn().mockResolvedValue({
success: true,
markdown: "# Hello World\n\nSample content from mock",
metadata: { title: "Hello World", sourceURL: "https://example.com" },
}),
crawlUrl: vi.fn().mockResolvedValue({
success: true,
data: [
{
markdown: "# Page 1",
metadata: { sourceURL: "https://example.com/page1" },
},
],
}),
mapUrl: vi.fn().mockResolvedValue({
success: true,
links: ["https://example.com/a", "https://example.com/b"],
}),
})),
}));
import { scrapeAndProcess } from "../src/scraper";
describe("Scraper", () => {
it("returns cleaned markdown", async () => {
const result = await scrapeAndProcess("https://example.com");
expect(result.markdown).toContain("Hello World");
expect(result.metadata.title).toBe("Hello World");
});
});
```
### Step 5: Integration Tests Against Local Instance
```typescript
// tests/integration.test.ts
import { describe, it, expect } from "vitest";
import FirecrawlApp from "@mendable/firecrawl-js";
const FIRECRAWL_URL = process.env.FIRECRAWL_API_URL || "http://localhost:3002";
describe.skipIf(!process.env.FIRECRAWL_API_URL)("Firecrawl Integration", () => {
const firecrawl = new FirecrawlApp({
apiKey: "fc-test",
apiUrl: FIRECRAWL_URL,
});
it("scrapes a page to markdown", async () => {
const result = await firecrawl.scrapeUrl("https://example.com", {
formats: ["markdown"],
});
expect(result.success).toBe(true);
expect(result.markdown).toBeDefined();
expect(result.markdown!.length).toBeGreaterThan(50);
}, 30000);
});
```
### Step 6: Dev Scripts
```json
{
"scripts": {
"dev": "tsx watch src/index.ts",
"test": "vitest",
"test:watch": "vitest --watch",
"test:integration": "FIRECRAWL_API_URL=http://localhost:3002 vitest run tests/integration",
"firecrawl:up": "docker compose up -d",
"firecrawl:down": "docker compose down",
"firecrawl:logs": "docker compose logs -f firecrawl"
}
}
```
## Output
- Self-hosted Firecrawl running on `localhost:3002`
- Unit tests with mocked SDK (zero API calls)
- Integration tests against local instance
- Hot-reload dev server with `tsx watch`
## Error Handling
| Error | Cause | Solution |
|-------|-------|----------|
| Docker `ECONNREFUSED` | Container not running | `docker compose up -d` |
| Redis connection refused | Redis not healthy yet | Wait for healthcheck, retry |
| `MODULE_NOT_FOUND` | Missing dependency | `npm install @mendable/firecrawl-js` |
| Integration test timeout | Self-hosted Firecrawl slow | Increase vitest timeout to 30s |
| Port 3002 in use | Another process | `lsof -i :3002` and kill, or change port |
## Examples
### Quick Scrape Script for Dev
```typescript
// scripts/dev-scrape.ts
import { getFirecrawl } from "../src/config";
const firecrawl = getFirecrawl();
const result = await firecrawl.scrapeUrl(process.argv[2] || "https://example.com", {
formats: ["markdown"],
});
console.log(result.markdown);
```
```bash
npx tsx scripts/dev-scrape.ts https://docs.firecrawl.dev
```
## Resources
- [Firecrawl Self-Hosting](https://docs.firecrawl.dev/contributing/self-host)
- [Vitest Documentation](https://vitest.dev/)
- [tsx (TypeScript Execute)](https://github.com/privatenumber/tsx)
## Next Steps
See `firecrawl-sdk-patterns` for production-ready code patterns.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!