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/ 但没有挂载
⚠️ 注意事项
- 不要将数据库文件放在容器可写层——使用 Volume 或 bind mount
- WAL 文件需要在同一文件系统——
.db、.wal、.shm必须在一起 - 多容器共享同一数据库文件需要谨慎——使用 WAL 模式并确保文件系统支持锁
- 不要在 NFS 上使用 SQLite——文件锁不可靠
- 容器重启时确保 checkpoint 完成——避免 WAL 文件过大
- 健康检查使用数据库连接——验证数据库可用性
💡 技巧
- Litestream 提供了近乎实时的流式备份——无需停机
- bind mount 性能最好——适合开发和高性能场景
- Volume 命名便于管理——
docker volume ls可以查看 - 多阶段构建减小镜像大小——编译和运行分离
- 只读副本适合读密集场景——
-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