强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Buku 书签管理完全指南 / 第 11 章:Docker 部署

第 11 章:Docker 部署

学习在 Docker 中部署 Buku Web 服务,实现数据持久化、容器编排和自动化备份。

11.1 Docker 镜像

使用官方镜像

# 拉取官方镜像
docker pull jarun/buku

# 查看镜像信息
docker images jarun/buku
# REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
# jarun/buku   latest    abc123def456   2 months ago   150MB

# 运行一次性命令
docker run --rm jarun/buku --version

构建自定义镜像

# Dockerfile
FROM python:3.11-slim

# 设置工作目录
WORKDIR /app

# 安装依赖
RUN pip3 install --no-cache-dir \
    buku \
    cryptography \
    flask \
    flask-admin

# 创建数据目录
RUN mkdir -p /root/.local/share/buku

# 暴露 Web 服务端口
EXPOSE 8080

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8080/api/bookmarks || exit 1

# 默认命令:启动 Web 服务
CMD ["buku", "--sr", "8080"]
# 构建镜像
docker build -t buku-custom .

# 运行容器
docker run -d \
    --name buku \
    -p 8080:8080 \
    -v ~/buku_data:/root/.local/share/buku \
    buku-custom

11.2 基本容器操作

运行容器

# 后台运行 Web 服务
docker run -d \
    --name buku \
    -p 8080:8080 \
    -v ~/buku_data:/root/.local/share/buku \
    jarun/buku --sr 8080

# 查看运行状态
docker ps | grep buku

# 查看日志
docker logs buku
docker logs -f buku  # 实时跟踪

# 进入容器
docker exec -it buku /bin/bash

CLI 操作

# 添加书签
docker run --rm \
    -v ~/buku_data:/root/.local/share/buku \
    jarun/buku -a https://example.com "示例" ,test

# 列出书签
docker run --rm \
    -v ~/buku_data:/root/.local/share/buku \
    jarun/buku -p

# 搜索书签
docker run --rm \
    -v ~/buku_data:/root/.local/share/buku \
    jarun/buku -s python

# 导入书签
docker run --rm \
    -v ~/buku_data:/root/.local/share/buku \
    -v ~/Downloads:/imports \
    jarun/buku -i /imports/bookmarks.html

容器管理

# 停止容器
docker stop buku

# 启动容器
docker start buku

# 重启容器
docker restart buku

# 删除容器
docker rm buku

# 强制删除运行中的容器
docker rm -f buku

11.3 数据持久化

Volume 挂载

# 使用 bind mount(推荐)
docker run -d \
    --name buku \
    -p 8080:8080 \
    -v /home/user/buku_data:/root/.local/share/buku \
    jarun/buku --sr 8080

# 使用 Docker volume
docker volume create buku_data

docker run -d \
    --name buku \
    -p 8080:8080 \
    -v buku_data:/root/.local/share/buku \
    jarun/buku --sr 8080

# 查看 volume
docker volume ls | grep buku
docker volume inspect buku_data

权限管理

# 确保宿主机目录权限正确
mkdir -p ~/buku_data
chmod 755 ~/buku_data

# 使用 --user 参数指定用户
docker run -d \
    --name buku \
    --user $(id -u):$(id -g) \
    -p 8080:8080 \
    -v ~/buku_data:/root/.local/share/buku \
    jarun/buku --sr 8080

备份数据

# 备份容器数据
docker cp buku:/root/.local/share/buku/bookmarks.db ./bookmarks_backup.db

# 备份整个数据目录
tar czf buku_backup_$(date +%Y%m%d).tar.gz ~/buku_data/

# 从备份恢复
docker cp ./bookmarks_backup.db buku:/root/.local/share/buku/bookmarks.db
docker restart buku

11.4 Docker Compose

基本 Compose 配置

# docker-compose.yml
version: '3.8'

services:
  buku:
    image: jarun/buku
    container_name: buku
    restart: unless-stopped
    ports:
      - "8080:8080"
    volumes:
      - ./data:/root/.local/share/buku
    command: ["buku", "--sr", "8080"]
# 启动服务
docker-compose up -d

# 查看状态
docker-compose ps

# 查看日志
docker-compose logs -f

# 停止服务
docker-compose down

完整 Compose 配置

# docker-compose.yml
version: '3.8'

services:
  buku:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: buku
    restart: unless-stopped
    ports:
      - "${BUKU_PORT:-8080}:8080"
    volumes:
      - buku_data:/root/.local/share/buku
    environment:
      - TZ=Asia/Shanghai
      - BUKU_SERVER_TOKEN=${BUKU_TOKEN}
    networks:
      - buku_net
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/api/bookmarks"]
      interval: 30s
      timeout: 3s
      start_period: 5s
      retries: 3

  # 可选:Nginx 反向代理
  nginx:
    image: nginx:alpine
    container_name: buku_nginx
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - buku
    networks:
      - buku_net

volumes:
  buku_data:
    driver: local

networks:
  buku_net:
    driver: bridge

环境变量配置

# .env 文件
BUKU_PORT=8080
BUKU_TOKEN=my_secret_token
TZ=Asia/Shanghai
# docker-compose.yml 引用环境变量
services:
  buku:
    image: jarun/buku
    ports:
      - "${BUKU_PORT}:8080"
    environment:
      - TZ=${TZ}

11.5 生产环境部署

安全配置

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

services:
  buku:
    image: jarun/buku
    container_name: buku
    restart: always
    # 不直接暴露端口,通过 Nginx 反向代理
    # ports:
    #   - "8080:8080"
    volumes:
      - buku_data:/root/.local/share/buku
    networks:
      - internal
    command: ["buku", "--sr", "8080", "--sall", "token:${BUKU_TOKEN}"]
    deploy:
      resources:
        limits:
          cpus: '0.50'
          memory: 256M

  nginx:
    image: nginx:alpine
    container_name: buku_nginx
    restart: always
    ports:
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/nginx/certs:ro
      - ./nginx/logs:/var/log/nginx
    depends_on:
      - buku
    networks:
      - internal
      - external
    deploy:
      resources:
        limits:
          cpus: '0.25'
          memory: 128M

volumes:
  buku_data:

networks:
  internal:
    driver: bridge
  external:
    driver: bridge

Nginx 反向代理配置

# nginx/nginx.conf
events {
    worker_connections 1024;
}

http {
    upstream buku_backend {
        server buku:8080;
    }

    # HTTPS 服务器
    server {
        listen 443 ssl http2;
        server_name buku.example.com;

        ssl_certificate /etc/nginx/certs/fullchain.pem;
        ssl_certificate_key /etc/nginx/certs/privkey.pem;
        ssl_protocols TLSv1.2 TLSv1.3;

        # 安全头
        add_header X-Frame-Options "SAMEORIGIN" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header X-XSS-Protection "1; mode=block" always;
        add_header Strict-Transport-Security "max-age=31536000" always;

        location / {
            proxy_pass http://buku_backend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        # API 速率限制
        location /api/ {
            limit_req zone=api burst=20 nodelay;
            proxy_pass http://buku_backend;
        }
    }

    # HTTP 重定向到 HTTPS
    server {
        listen 80;
        server_name buku.example.com;
        return 301 https://$server_name$request_uri;
    }
}

11.6 自动化备份

备份脚本

#!/bin/bash
# backup_buku_docker.sh - Docker Buku 自动备份

BACKUP_DIR="./backups"
DATE=$(date +%Y%m%d_%H%M%S)
CONTAINER_NAME="buku"

mkdir -p "$BACKUP_DIR"

# 方法一:从容器复制
docker cp "$CONTAINER_NAME":/root/.local/share/buku/bookmarks.db \
    "$BACKUP_DIR/bookmarks_$DATE.db"

# 方法二:使用 docker exec
docker exec "$CONTAINER_NAME" \
    sqlite3 /root/.local/share/buku/bookmarks.db ".backup /tmp/backup.db"
docker cp "$CONTAINER_NAME":/tmp/backup.db "$BACKUP_DIR/bookmarks_$DATE.db"

# 压缩备份
gzip "$BACKUP_DIR/bookmarks_$DATE.db"

# 清理 30 天前的备份
find "$BACKUP_DIR" -name "*.db.gz" -mtime +30 -delete

echo "备份完成: $BACKUP_DIR/bookmarks_$DATE.db.gz"

Cron 定时备份

# docker-compose.yml 中添加备份服务
services:
  # ... 其他服务 ...

  backup:
    image: alpine
    container_name: buku_backup
    volumes:
      - ./backup.sh:/backup.sh:ro
      - ./backups:/backups
      - /var/run/docker.sock:/var/run/docker.sock
    entrypoint: /bin/sh
    command: -c "echo '0 2 * * * /backup.sh' | crontab - && crond -f -l 8"
    depends_on:
      - buku

11.7 监控与日志

日志管理

# docker-compose.yml
services:
  buku:
    image: jarun/buku
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

健康检查

services:
  buku:
    image: jarun/buku
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/api/bookmarks"]
      interval: 30s
      timeout: 3s
      start_period: 5s
      retries: 3

监控脚本

#!/bin/bash
# monitor_buku.sh - 监控 Buku 容器状态

check_container() {
    local status=$(docker inspect --format='{{.State.Status}}' buku 2>/dev/null)
    local health=$(docker inspect --format='{{.State.Health.Status}}' buku 2>/dev/null)

    echo "容器状态: $status"
    echo "健康状态: $health"

    if [ "$status" != "running" ]; then
        echo "⚠️ 容器未运行,正在重启..."
        docker start buku
    fi

    if [ "$health" = "unhealthy" ]; then
        echo "⚠️ 容器不健康,查看日志..."
        docker logs --tail 20 buku
    fi
}

check_container

11.8 常见问题

问题原因解决方案
数据丢失未挂载 volume确保正确挂载数据目录
权限错误容器用户不匹配使用 --user 参数
端口冲突端口被占用更换端口或停止冲突服务
启动失败数据库损坏从备份恢复数据库
性能问题资源限制过严调整 CPU/内存限制
网络问题容器网络配置错误检查 Docker 网络设置

11.9 本章小结

要点说明
官方镜像jarun/buku
数据目录/root/.local/share/buku
持久化使用 Volume 或 bind mount
Compose推荐使用 docker-compose 管理
备份定期备份 bookmarks.db
安全使用 Token 认证 + HTTPS

扩展阅读


下一章第 12 章:最佳实践 — 学习 Buku 的最佳实践,包括工作流设计、标签策略和同步方案。