强曰为道

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

17 - 容器化

17 - 容器化:Docker 中使用 SQLite 与数据持久化

17.1 Docker 中使用 SQLite 的特点

SQLite 是进程内数据库,Docker 中的使用方式与客户端-服务器数据库不同:

特性PostgreSQL (容器)SQLite (容器)
服务进程独立进程嵌入应用
数据持久化Volume 挂载数据目录Volume 挂载数据库文件
网络访问通过端口映射无(仅本地)
备份pg_dump复制 .db 文件
并发容器内多连接应用内多连接

17.2 基本 SQLite Docker 镜像

17.2.1 使用官方镜像

# 运行 SQLite CLI 交互式 Shell
docker run -it --rm -v $(pwd)/data:/data keinos/sqlite3 sqlite3 /data/mydb.db

# 从 Docker Hub 获取 SQLite 镜像
docker pull nouchka/sqlite3
docker run -it --rm nouchka/sqlite3

17.2.2 自定义 Dockerfile

# Dockerfile - 带 SQLite 的应用镜像
FROM python:3.12-slim

# 安装 SQLite3 CLI(用于维护和调试)
RUN apt-get update && apt-get install -y sqlite3 && rm -rf /var/lib/apt/lists/*

# 安装 Python 依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 复制应用代码
COPY app/ /app/
WORKDIR /app

# 创建数据目录
RUN mkdir -p /data

# 设置环境变量
ENV DATABASE_PATH=/data/app.db

# 暴露端口(如果是 Web 应用)
EXPOSE 8000

CMD ["python", "main.py"]

17.2.3 多阶段构建

# 构建阶段
FROM python:3.12 AS builder
WORKDIR /build
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

# 运行阶段
FROM python:3.12-slim
RUN apt-get update && apt-get install -y sqlite3 && rm -rf /var/lib/apt/lists/*

COPY --from=builder /install /usr/local
COPY app/ /app/

WORKDIR /app
ENV DATABASE_PATH=/data/app.db
VOLUME ["/data"]

CMD ["python", "main.py"]

17.3 数据持久化

17.3.1 使用 Volume 挂载

# 创建命名 Volume
docker volume create sqlite_data

# 使用 Volume 运行容器
docker run -d \
    --name myapp \
    -v sqlite_data:/data \
    -p 8000:8000 \
    myapp:latest

# 使用 bind mount(挂载宿主机目录)
docker run -d \
    --name myapp \
    -v /var/data/myapp:/data \
    -p 8000:8000 \
    myapp:latest

17.3.2 Docker Compose

# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    container_name: myapp
    ports:
      - "8000:8000"
    volumes:
      - sqlite_data:/data
      - ./config:/app/config:ro
    environment:
      - DATABASE_PATH=/data/app.db
      - PYTHONUNBUFFERED=1
    restart: unless-stopped

    # 健康检查
    healthcheck:
      test: ["CMD", "python", "-c", "import sqlite3; sqlite3.connect('/data/app.db')"]
      interval: 30s
      timeout: 10s
      retries: 3

volumes:
  sqlite_data:
    driver: local

17.3.3 数据备份

# 备份容器中的数据库
docker exec myapp sqlite3 /data/app.db ".backup /data/backup.db"
docker cp myapp:/data/backup.db ./backup.db

# 或者直接备份 Volume
docker run --rm -v sqlite_data:/data -v $(pwd):/backup alpine \
    tar czf /backup/sqlite_backup_$(date +%Y%m%d).tar.gz -C /data .

# 恢复备份
docker run --rm -v sqlite_data:/data -v $(pwd):/backup alpine \
    tar xzf /backup/sqlite_backup_20260510.tar.gz -C /data

17.4 完整 Web 应用示例

17.4.1 Flask 应用

# app/main.py
import os
import sqlite3
from flask import Flask, request, jsonify, g

app = Flask(__name__)
DATABASE = os.environ.get('DATABASE_PATH', '/data/app.db')

def get_db():
    if 'db' not in g:
        g.db = sqlite3.connect(DATABASE)
        g.db.row_factory = sqlite3.Row
        g.db.execute('PRAGMA journal_mode = WAL')
        g.db.execute('PRAGMA foreign_keys = ON')
        g.db.execute('PRAGMA busy_timeout = 5000')
    return g.db

@app.teardown_appcontext
def close_db(exception):
    db = g.pop('db', None)
    if db is not None:
        db.close()

def init_db():
    db = sqlite3.connect(DATABASE)
    db.executescript('''
        CREATE TABLE IF NOT EXISTS todos (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            title TEXT NOT NULL,
            completed INTEGER DEFAULT 0,
            created_at TEXT DEFAULT (datetime('now'))
        )
    ''')
    db.commit()
    db.close()

@app.route('/todos', methods=['GET'])
def list_todos():
    db = get_db()
    todos = db.execute('SELECT * FROM todos ORDER BY id DESC').fetchall()
    return jsonify([dict(t) for t in todos])

@app.route('/todos', methods=['POST'])
def create_todo():
    db = get_db()
    title = request.json.get('title')
    if not title:
        return jsonify({'error': 'title required'}), 400
    db.execute('INSERT INTO todos (title) VALUES (?)', (title,))
    db.commit()
    return jsonify({'message': 'created'}), 201

if __name__ == '__main__':
    init_db()
    app.run(host='0.0.0.0', port=8000, debug=False)
# requirements.txt
flask==3.0.0
# Dockerfile
FROM python:3.12-slim

RUN apt-get update && apt-get install -y sqlite3 && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY main.py .

RUN mkdir -p /data
ENV DATABASE_PATH=/data/app.db
EXPOSE 8000

CMD ["python", "main.py"]

17.5 Go 应用示例

17.5.1 Go Dockerfile

# 多阶段构建
FROM golang:1.22-alpine AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=1 go build -o server .

FROM alpine:3.19
RUN apk add --no-cache sqlite-libs ca-certificates

COPY --from=builder /app/server /usr/local/bin/

RUN mkdir -p /data
ENV DATABASE_PATH=/data/app.db
EXPOSE 8080

CMD ["server"]

17.6 多实例与只读副本

17.6.1 只读副本

# docker-compose.yml - 读写分离
version: '3.8'

services:
  app:
    build: .
    volumes:
      - sqlite_data:/data
    environment:
      - DATABASE_PATH=/data/app.db

  # 只读副本(用于读取密集的查询)
  app-readonly:
    build: .
    volumes:
      - sqlite_data:/data:ro  # 只读挂载
    environment:
      - DATABASE_PATH=/data/app.db
      - READONLY=true
    deploy:
      replicas: 3

volumes:
  sqlite_data:

17.7 SQLite + Litestream 流式备份

Litestream 是 SQLite 的流式备份工具,可以实时复制到 S3、GCS 等。

# Dockerfile with Litestream
FROM litestream/litestream:latest AS litestream

FROM python:3.12-slim

# 安装 Litestream
COPY --from=litestream /usr/local/bin/litestream /usr/local/bin/litestream

RUN apt-get update && apt-get install -y sqlite3 && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app/ .

COPY litestream.yml /etc/litestream.yml

RUN mkdir -p /data

CMD ["litestream", "replicate", "-exec", "python main.py"]
# litestream.yml
dbs:
  - path: /data/app.db
    replicas:
      - url: s3://mybucket/db/myapp
        access-key-id: ${AWS_ACCESS_KEY_ID}
        secret-access-key: ${AWS_SECRET_ACCESS_KEY}

17.8 性能注意事项

17.8.1 Docker 存储驱动

存储驱动SQLite 性能说明
overlay2✅ 好默认驱动,推荐
bind mount✅ 最好直接挂载宿主机目录
Volume✅ 好Docker 管理的存储
NFS volume❌ 差网络延迟影响性能

⚠️ 不要将数据库文件放在容器的可写层——性能差且数据不持久。

17.8.2 最佳实践

# ✅ 推荐:使用命名 Volume 或 bind mount
docker run -v sqlite_data:/data myapp

# ❌ 不推荐:使用 tmpfs(数据不持久)
docker run --tmpfs /data myapp

# ❌ 不推荐:数据库在容器内(重启丢失)
# 数据库文件在 /app/data/ 但没有挂载

⚠️ 注意事项

  1. 不要将数据库文件放在容器可写层——使用 Volume 或 bind mount
  2. WAL 文件需要在同一文件系统——.db.wal.shm 必须在一起
  3. 多容器共享同一数据库文件需要谨慎——使用 WAL 模式并确保文件系统支持锁
  4. 不要在 NFS 上使用 SQLite——文件锁不可靠
  5. 容器重启时确保 checkpoint 完成——避免 WAL 文件过大
  6. 健康检查使用数据库连接——验证数据库可用性

💡 技巧

  1. Litestream 提供了近乎实时的流式备份——无需停机
  2. bind mount 性能最好——适合开发和高性能场景
  3. Volume 命名便于管理——docker volume ls 可以查看
  4. 多阶段构建减小镜像大小——编译和运行分离
  5. 只读副本适合读密集场景——-v data:/data:ro

📌 业务场景

场景一:内部工具

# docker-compose.yml
version: '3.8'
services:
  adminer:
    image: adminer
    ports: ["8080:8080"]
  app:
    build: .
    volumes:
      - app_data:/data
    ports: ["3000:3000"]

volumes:
  app_data:

场景二:边缘计算设备

# 轻量级镜像用于 IoT 设备
FROM alpine:3.19
RUN apk add --no-cache sqlite python3 py3-pip
COPY collector.py /app/
CMD ["python3", "/app/collector.py"]

🔗 扩展阅读


📖 下一章18 - 驱动集成 —— Python、Go、Java、Node.js、Rust