When "Deploy with Docker" Goes Wrong: A Firecrawl Cryptominer Story

31/10/2025 — Ayyaz Zafar Docker
When "Deploy with Docker" Goes Wrong: A Firecrawl Cryptominer Story

TL;DR: I deployed Firecrawl's official docker-compose for web scraping. Within 12 hours, my server was compromised with a cryptominer consuming 764% CPU. Here's what happened, how Firecrawl's default configuration enabled it, and how to protect yourself.


The Setup

I run a 16GB Hetzner server hosting several self-hosted AI services: Open WebUI, n8n, LiteLLM, and SearXNG. Everything was running smoothly with a server load averaging 0.72 and RAM usage around 3.4GB.

I needed web scraping capabilities for Open WebUI, and Firecrawl seemed perfect:

  • ⭐ 65,600+ GitHub stars
  • 🏢 Professional team (Mendable AI)
  • 📦 4,499+ commits, active development
  • 🤝 Used by LangChain, LlamaIndex, and major AI frameworks

The documentation was clear: clone the repo, run docker compose up -d, and you're done. Standard deployment process.

October 30, evening: Deployed Firecrawl following official documentation.

October 31, morning: Something was terribly wrong.


The Discovery

$ ssh root@nodes.ayyaztech.com
$ uptime
06:15:49 up 1 day, 14:22, load average: 8.11, 8.33, 8.36

My normally quiet server had a load average of 8.11. For context, it's usually under 1.0.

$ free -h
              total        used        free
Mem:           15Gi        7.1Gi       740Mi

RAM usage had jumped from 3.4GB to 7.1GB. CPU fans were at maximum. Something was mining cryptocurrency on my server.


The Smoking Gun

I checked the Firecrawl PostgreSQL container:

$ docker exec nuq-postgres-... ps aux

USER   PID  %CPU  %MEM     VSZ   RSS  COMMAND
postgres  1442   0.0   0.0  709388  13320  /tmp/kinsing
postgres  1769  764.0  15.1 3379788 2415080  /tmp/kdevtmpfsi

Two malicious processes:

  • kinsing - Malware dropper (0% CPU, waiting)
  • kdevtmpfsi - Cryptominer (764% CPU, using 8 cores!)

The cryptominer had been running for 90 hours straight, mining cryptocurrency at my expense.


How Did This Happen?

Let me show you Firecrawl's official docker-compose.yaml:

postgres:
  image: postgres:15
  ports:
    - "5432:5432"  # ← EXPOSED TO THE INTERNET
  environment:
    POSTGRES_USER: postgres      # ← DEFAULT USERNAME
    POSTGRES_PASSWORD: postgres  # ← DEFAULT PASSWORD
    POSTGRES_DB: postgres

The vulnerability:

  1. PostgreSQL port exposed to 0.0.0.0:5432 (accessible from anywhere)
  2. Default credentials: postgres/postgres (industry-standard weak password)
  3. pg_cron extension enabled (perfect for malware persistence)
  4. No firewall requirements documented

Within 12 hours, automated scanners:

  1. Found my server's exposed PostgreSQL port
  2. Tried default credentials (postgres/postgres)
  3. Gained access ✅
  4. Exploited pg_cron to install malware
  5. Started mining cryptocurrency

This is a known attack pattern. The malware signature (kinsing + kdevtmpfsi) specifically targets exposed databases on cloud servers.


The Response

Immediate Actions:

# 1. Stop all Firecrawl containers
docker stop api-... nuq-postgres-... playwright-... redis-...

# Server load dropped immediately: 8.11 → 0.73 ✅
# RAM freed: 3.7GB ✅

Security Audit:

# 2. Scan ALL other containers for malware
for container in $(docker ps --format '{{.Names}}'); do
  docker exec $container ps aux | grep -E "(kdevtmpfsi|kinsing)"
done
# Result: Clean ✅ (malware contained to Firecrawl)

# 3. Check host system
ps aux | grep -E "(kdevtmpfsi|kinsing)"
# Result: Clean ✅ (Docker isolation worked)

# 4. Verify SSH keys
cat ~/.ssh/authorized_keys
# Result: No backdoors ✅

Complete Removal:

# 5. Remove containers, images, networks
docker rm -f api-... nuq-postgres-... playwright-... redis-...
docker rmi -f firecrawl-api firecrawl-postgres ...
docker network rm firecrawl-network

# 6. Clean up database
docker exec postgres psql -U postgres -c 'DROP DATABASE firecrawl;'
docker exec postgres psql -U postgres -c 'DROP USER firecrawl_user;'

Final Result:

  • Server load: 0.13 (back to healthy)
  • RAM usage: 3.4GB (back to normal)
  • All services: Operational ✅
  • Malware: Completely removed ✅

Is Firecrawl Bad?

No. Let me be clear about this.

Firecrawl (the project) is legitimate:

  • 65,600+ stars, professionally maintained
  • Used in production by major companies
  • Clean, well-documented codebase
  • Active community and development

Firecrawl (the cloud API) is safe:

  • Managed infrastructure
  • No self-hosted security risks
  • Professional security team

The problem: Firecrawl's default self-hosted configuration has dangerous security settings that most users won't catch.

This is a configuration issue, not a code issue.


Why This Matters

I'm not the only one affected by this. Here's why this is critical:

1. Scale of Impact

  • 65,600+ stars = thousands of deployments
  • Default configs are typically not changed
  • Most users trust official documentation
  • "Deploy with Docker" implies it's production-ready

2. Attack Timeline

Oct 30, 8:00 PM  - Deployed Firecrawl
Oct 31, 8:00 AM  - Already compromised (12 hours)

Automated scanners are fast. They're constantly scanning the internet for:

  • Exposed PostgreSQL ports (5432)
  • Exposed MySQL ports (3306)
  • Exposed Redis ports (6379)
  • Default credentials

3. Cost Beyond Compute

My incident cost:

  • ~$5-10 in wasted compute (90 hours mining)
  • 6 hours investigating and cleaning up
  • Potential data exposure (if malware had been more sophisticated)

But the real cost is trust. When official repos have dangerous defaults, it erodes confidence in open-source deployment.


The Bigger Problem: Docker Security Culture

This incident highlights a broader issue in the Docker ecosystem:

Dangerous Defaults Are Common

How many docker-compose.yaml files have you seen with:

ports:
  - "5432:5432"  # Exposed to internet
  - "3306:3306"  # Exposed to internet
  - "6379:6379"  # Exposed to internet

These should almost never be exposed unless you have specific reasons and proper firewall rules.

Better Default Configuration

This is what it should look like:

postgres:
  image: postgres:15
  # NO PORT MAPPING - internal only
  environment:
    POSTGRES_USER: ${POSTGRES_USER}  # Required .env file
    POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}  # Required strong password
    POSTGRES_DB: ${POSTGRES_DB}
  networks:
    - internal  # Separate internal network

Security Warnings Are Missing

The README should include:

⚠️ SECURITY WARNING:

Before deploying to production:
1. Change ALL default credentials
2. Remove port mappings for databases
3. Configure firewall rules
4. Use strong, unique passwords
5. Keep databases on internal networks only

See SECURITY.md for details.

How to Protect Yourself

If you're self-hosting any Docker-based application:

1. Audit Before Deploy

# Check what ports are being exposed
grep -r "ports:" docker-compose.yml

# Look for hardcoded credentials
grep -r "PASSWORD" docker-compose.yml .env*

# Check for dangerous bindings (0.0.0.0)
grep -r "0.0.0.0" docker-compose.yml

2. Set Resource Limits

Add this to every container:

deploy:
  resources:
    limits:
      cpus: '2.0'        # Limit CPU usage
      memory: 1G         # Limit RAM usage
    reservations:
      memory: 512M

This prevents runaway processes from consuming all resources.

3. Monitor Actively

# Check resource usage
docker stats --no-stream

# Look for suspicious processes
docker exec CONTAINER ps aux | grep tmp

# Monitor server load
uptime

Set up alerts for:

  • Load average > 2x CPU core count
  • Unexpected CPU spikes (>100% single core)
  • Unusual RAM usage patterns

4. Use Internal Networks

networks:
  internal:
    driver: bridge
    internal: true  # No external access

services:
  postgres:
    networks:
      - internal  # Only accessible by other containers

5. Implement Defense in Depth

  • Firewall: Block all ports except 22, 80, 443
  • Fail2ban: Auto-ban brute force attempts
  • Regular updates: Keep Docker and images updated
  • Minimal privileges: Don't run as root inside containers
  • Read-only filesystems: Where possible

Red Flags to Watch For

These are signs your container might be compromised:

CPU/RAM Spikes

# Normal
load average: 0.5, 0.6, 0.7

# Suspicious
load average: 8.11, 8.33, 8.36  # 🚨

Unusual Processes

# Look for processes in /tmp/ with random names
/tmp/kdevtmpfsi  # 🚨
/tmp/kinsing     # 🚨
/tmp/xmrig       # 🚨

Network Connections

# Check for unexpected outbound connections
netstat -tuln | grep ESTABLISHED

# Mining pools typically use ports 3333, 4444, 8080

File System Changes

# New files in /tmp/
# New cron jobs
# Modified system binaries

What Firecrawl Should Do

I'm reporting this responsibly to the Firecrawl team. Here are my recommendations:

1. Fix Default Configuration (Critical)

# Remove port exposure
postgres:
  image: postgres:15
  # Remove: ports: - "5432:5432"
  environment:
    POSTGRES_USER: ${POSTGRES_USER:-Change this!}
    POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Password required}

2. Add Security Documentation

Create a SECURITY.md with:

  • Deployment security checklist
  • Firewall configuration guide
  • Credential management best practices
  • Incident response procedures

3. Provide Secure Templates

templates/
├── docker-compose.dev.yml   # For development (exposed ports OK)
├── docker-compose.prod.yml  # For production (secure by default)
└── docker-compose.secure.yml # Hardened configuration

4. Add Security Warnings

Update README.md with prominent warnings about exposed services.


Lessons Learned

For Developers:

  1. Secure by default - Security should be opt-out, not opt-in
  2. Never hardcode credentials - Always use environment variables
  3. Minimize exposed ports - Internal networking should be the default
  4. Document security implications - Users need to know the risks
  5. Provide secure examples - Show users the right way

For Users:

  1. Never trust defaults - Always audit before deploying
  2. Monitor resource usage - Quick spikes are red flags
  3. Use official images - Prefer postgres:16-alpine over custom builds
  4. Implement layered security - Firewall, monitoring, limits
  5. Keep it simple - Complexity increases attack surface

For Me:

  1. ✅ Always audit third-party configs - Even from trusted sources
  2. ✅ Set resource limits on everything - Prevent resource abuse
  3. ✅ Monitor actively - Daily checks of docker stats and uptime
  4. ✅ Use managed services when possible - Firecrawl cloud API is safer
  5. ✅ Document everything - Future me will thank present me

The Alternative

The irony? I deployed Firecrawl for web scraping in Open WebUI.

Turns out: Open WebUI has built-in web scraping that works perfectly. I didn't need Firecrawl at all.

Sometimes the best security decision is not deploying something.

If you do need advanced web scraping:

  • Use Firecrawl's cloud API (https://api.firecrawl.dev) - Secure, managed
  • Use Open WebUI Pipelines - Built-in, no external service needed
  • Self-host with caution - Follow the security checklist above

Final Thoughts

This was a learning experience, not a disaster. Docker's container isolation worked perfectly - the malware never escaped. SSH remained secure. Other services were unaffected. Response time was quick.

But it highlights a critical issue: dangerous defaults in official documentation.

When projects with 65,000+ stars ship configurations that expose databases with default credentials, thousands of users are potentially affected. Most won't check. Most will trust the official docs. Most won't notice until it's too late.

We can do better.

If you're maintaining a self-hosted application:

  1. Review your docker-compose.yaml for security issues
  2. Add security warnings to your README
  3. Provide secure configuration templates
  4. Make security the default, not an afterthought

And if you're deploying self-hosted applications:

  1. Audit before you deploy
  2. Monitor after you deploy
  3. Be ready to respond when things go wrong

Stay safe out there. 🛡️


Resources

Share this post.
Stay up-to-date

Subscribe to our newsletter

Don't miss this

You might also like