Docker containerized crawler - Detailed explanation of cloud native crawler deployment and management

📂 Stage: Stage 6 - Operation, Maintenance and Monitoring (Engineering) 🔗 Related chapters: Scrapyd与ScrapydWeb · 抓取监控看板 · Scrapy-Redis分布式架构 📌 Advanced tips: Kubernetes cluster deployment and complete CI/CD pipeline are recommended at the end of the article.


Table of contents


Why containerize Scrapy

Scrapy's dependency environment is picky, system-level libraries (such aslxml) requires compilation, and Python package versions are prone to conflicts. In traditional deployment, "it works on my machine, but an error occurs when I go to your machine" is almost the norm. Docker containerization can solve these problems all at once:

  1. Environment consistency: development, testing, and production use exactly the same image, and all dependent versions are locked.
  2. Fast deployment and scaling: One command can start the service, and horizontal expansion only requires adjustmentsreplicasquantity.
  3. Clear resource isolation: Allocate independent CPU and memory to each crawler container to avoid mutual interference.
  4. Self-healing for faults: Configure an automatic restart policy, and the container will be automatically pulled up if it exits unexpectedly.

Best Dockerfile Design

Design principles

  • Use Official Lite Image (slim / alpine), the image size is small and the attack surface is small;
  • Optimize layer caching: put non-volatile dependency layers at the front and frequently modified code layers at the end;
  • Run as non-root user to improve security;
  • Set environment variables (disable generation.pyc, enable output buffering);
  • Configure health check to make container status observable.

Basic image and system dependencies

Here is a production-ready oneDockerfileBeginning, using Python 3.11slimversion, and install the compilation tools and libraries required by Scrapy.

# Dockerfile.scrapy-prod
FROM python:3.11-slim AS base

# 环境变量:不生成字节码、不缓冲输出、指定Scrapy配置模块路径
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    SCRAPY_SETTINGS_MODULE=mycrawler.settings \
    APP_HOME=/app

# 合并安装系统依赖,减少镜像层数
RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc g++ libxml2-dev libxslt1-dev libffi-dev libssl-dev zlib1g-dev ca-certificates \
    && rm -rf /var/lib/apt/lists/* \
    && apt-get clean

WORKDIR ${APP_HOME}

Multi-stage build optimization

slimAlthough the image is much smaller than the full version, we still installed a bunch of compilation tools during the build phase (gccg++wait). These tools are only needed when installing Python packages, not at runtime. Multi-stage build can completely separate "build dependencies" and "run dependencies", and the final image size can be reduced by more than 60%.

# 1️⃣ 构建阶段:只安装那些需要编译的Python包
FROM base AS builder

COPY requirements.txt .
RUN pip install --user --no-cache-dir --upgrade pip \
    && pip install --user --no-cache-dir -r requirements.txt

# 2️⃣ 生产阶段:只复制构建好的包和应用代码
FROM base AS production

# 复制构建阶段安装到用户目录的Python包
COPY --from=builder /root/.local /root/.local
ENV PATH=/root/.local/bin:$PATH

# 创建非root用户(UID:GID=1001,方便挂载卷时权限映射)
RUN groupadd -r scrapy --gid=1001 \
    && useradd -r -g scrapy --uid=1001 -d ${APP_HOME} scrapy \
    && chown -R scrapy:scrapy ${APP_HOME}

# 切换到非root用户
USER scrapy

# 复制应用代码(此时依赖层已构建,后续代码修改不会触发依赖重装)
COPY --chown=scrapy:scrapy . .

# 创建运行所需的目录
RUN mkdir -p logs data cache

# 暴露端口(例如Scrapyd默认6800)
EXPOSE 6800

# 健康检查:尝试访问Scrapyd的仪表页,失败则标记为不健康
HEALTHCHECK --interval=30s --timeout=10s --start-period=20s --retries=3 \
    CMD curl -f http://localhost:6800/ || exit 1

# 启动命令
ENTRYPOINT ["scrapyd"]
CMD ["--bind", "0.0.0.0"]

Use here insteadscrapydcommand to start and usecurlPerform health checks on Scrapyd web pages. If your image does not come withcurl, can be found inbasestage additional installationcurl, or usepython -c …custom script.


Docker Compose one-click orchestration

Crawler projects usually also rely on Redis (deduplication/task queue) and MongoDB (storage results). Docker Compose can be used to orchestrate all services together and start the entire architecture with one command.

# docker-compose.prod.yml
version: '3.8'

services:
  redis:
    image: redis:7-alpine
    container_name: scrapy-redis
    restart: unless-stopped
    expose:
      - "6379"               # 只在内网暴露
    volumes:
      - redis_data:/data
    command: redis-server --appendonly yes --requirepass scrapy123
    networks:
      - scrapy-internal

  mongodb:
    image: mongo:6-alpine
    container_name: scrapy-mongodb
    restart: unless-stopped
    expose:
      - "27017"
    environment:
      MONGO_INITDB_ROOT_USERNAME: scrapy
      MONGO_INITDB_ROOT_PASSWORD: scrapy123
      MONGO_INITDB_DATABASE: crawler_data
    volumes:
      - mongodb_data:/data/db
    networks:
      - scrapy-internal

  scrapyd:
    build:
      context: .
      dockerfile: Dockerfile.scrapy-prod
    restart: unless-stopped
    depends_on:
      - redis
      - mongodb
    environment:
      - REDIS_URL=redis://:scrapy123@redis:6379/0
      - MONGO_URI=mongodb://scrapy:scrapy123@mongodb:27017/crawler_data?authSource=admin
    volumes:
      - scrapyd_logs:/app/logs
      - scrapyd_data:/app/data
    deploy:
      replicas: 3                        # 启动3个Scrapyd实例
      resources:
        limits:
          cpus: '0.75'
          memory: 768M
        reservations:
          cpus: '0.25'
          memory: 256M
    networks:
      - scrapy-internal

  nginx:
    image: nginx:alpine
    container_name: scrapy-nginx
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - scrapyd
    networks:
      - scrapy-internal
      - scrapy-external

volumes:
  redis_data:
  mongodb_data:
  scrapyd_logs:
  scrapyd_data:

networks:
  scrapy-internal:
    driver: bridge
    internal: true           # 内部服务无法直接访问外网,提高安全性
  scrapy-external:
    driver: bridge

With a few simple lines of configuration, you will have a crawler cluster with load balancing and persistence.


Rapid deployment of production environment

The following is an automated deployment scriptdeploy.sh, it will be executed every time it is deployed: pull the code → build a new image → stop the old container → start the new container → clean up the garbage. Once it fails, it will automatically roll back to the previous version.

#!/bin/bash
# deploy.sh
set -euo pipefail

IMAGE_NAME="mycompany/scrapyd-cluster"
TAG=$(git rev-parse --short HEAD)   # 用git commit短号做标签,方便回滚
COMPOSE_FILE="docker-compose.prod.yml"

echo "🚀 开始部署 Scrapyd 集群 (Tag: $TAG)..."

git pull origin main

# 构建镜像并打上 latest 标签
docker build -t ${IMAGE_NAME}:${TAG} -f Dockerfile.scrapy-prod .
docker tag ${IMAGE_NAME}:${TAG} ${IMAGE_NAME}:latest

# 停止旧服务(保留数据卷)
docker-compose -f ${COMPOSE_FILE} down --remove-orphans

# 启动新服务(Scrapyd 实例数=3)
docker-compose -f ${COMPOSE_FILE} up -d --scale scrapyd=3

# 等待并验证
echo "⏳ 等待服务启动..."
sleep 20
if docker-compose -f ${COMPOSE_FILE} ps | grep -q "Up"; then
    echo "✅ 部署成功!"
else
    echo "❌ 部署失败!正在回滚到上一版本..."
    PREV_TAG=$(git rev-parse --short HEAD~1)
    docker tag ${IMAGE_NAME}:${PREV_TAG} ${IMAGE_NAME}:latest
    docker-compose -f ${COMPOSE_FILE} up -d --scale scrapyd=3
    exit 1
fi

# 清理不再使用的镜像
echo "🧹 清理 Docker 垃圾..."
docker image prune -f
docker container prune -f

echo "🎉 部署完成!"

Security Configuration and Permission Management

Containerization can bring a lot of convenience, but if security configuration is not in place, it may open up new risks. The following items are what must be done in the production environment.

1. Run by non-root user

Already created in Dockerfilescrapyuser, and specified UID/GID=1001 to facilitate permission mapping with the host machine.

2. Disable privileged containers

Docker Compose defaultprivileged: false, be sure not to turn it on automatically. Privileged containers can directly access the host kernel and are extremely destructive.

3. Read-only root file system

existscrapydAdd the following configuration to the service to make the container's root file system read-only./tmpand cache directories can be written using the memory file system (tmpfs).

read_only: true
tmpfs:
  - /tmp
  - /app/cache

4. Limit container capabilities

The default container retains a lot of kernel capabilities. We can remove all the unnecessary ones and only retain the ability to bind ports.

cap_drop:
  - ALL
cap_add:
  - NET_BIND_SERVICE

Basic monitoring and troubleshooting

Daily monitoring

  • Live Resources:docker statsYou can view the CPU, memory, and network consumption of all containers.
  • Live Log:docker logs -f <容器名>Dynamically track container output.
  • Task Status:curl http://localhost:6800/listjobs.json?project=myprojectView Scrapyd's task queue.

Quick troubleshooting script

Write these commonly used checking commands into a scripttroubleshoot.sh, which facilitates one-click diagnosis when problems arise.

#!/bin/bash
# troubleshoot.sh
echo "🔍 Scrapyd 集群故障排查开始..."

# 1. Docker 服务是否正常
if ! systemctl is-active --quiet docker; then
    echo "❌ Docker 服务未运行!"
    exit 1
fi

# 2. 列出所有容器状态
echo "📋 容器状态:"
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"

# 3. 查看异常容器日志
echo "📝 异常容器日志(最后20行):"
for container in $(docker ps -aq --filter "status=exited" --filter "status=restarting"); do
    name=$(docker inspect --format='{{.Name}}' $container | sed 's/\///')
    echo -e "\n--- $name ---"
    docker logs --tail 20 $container
done

# 4. 测试 Redis 连接
echo -e "\n🔌 Redis 连接测试:"
docker exec scrapy-redis redis-cli -a scrapy123 ping

# 5. 测试 MongoDB 连接
echo -e "\n🔌 MongoDB 连接测试:"
docker exec scrapy-mongodb mongosh -u scrapy -p scrapy123 --authenticationDatabase admin --eval "db.adminCommand('ping')"

If there is nomongoshorredis-cli, you can use insteadnc -zvorpingTest port connectivity.


Best Practice Summary

Dockerfile

✅ Choose the official slim/alpine image, which is small in size and highly secure. ✅ Use multi-stage construction, eliminate compilation tools, and compress the image size by 60%+ ✅ The dependency layer is in the front and the code layer is in the back, making full use of cache to speed up the build. ✅ Force the use of non-root users to reduce security risks ✅ Configure health checks to make container status transparent and controllable

Deployment and operation

✅ Mirror tags use Git short commit numbers and can be rolled back at any time ✅ Use Docker Compose to orchestrate all services and start and stop them with one click ✅ Strictly limit the CPU/memory upper limit and configure automatic restart ✅ The core services (Redis, MongoDB) are placed in the internal network and the ports are not exposed to the outside world.

Security reinforcement

✅ Disable privileged containers to minimize attack surface ✅ Mount the root file system as read-only and cooperate with tmpfs to process temporary files ✅ Cut the container kernel capabilities to only the necessary items ✅ Set strong passwords for databases and middleware


🏷️ tag cloud:Docker 容器化 Scrapy 云原生 Docker Compose 部署管理
📚 Expansion recommendations: Kubernetes集群部署爬虫 · GitHub Actions CI/CD流水线 · Prometheus+Grafana监控体系