fix(content): repair Django tutorial crash and complete the hardening sweep

Follow-ups to the 9 secret-hardening commits (each verified against
docs.docker.com / vendor docs; one outright new bug found and fixed):

- 11.6: eb5e4397 changed settings.py to a hard os.environ lookup but the
  web service never receives POSTGRES_PASSWORD (only db does; DATABASE_URL
  is set but never read) -> step-6 'docker compose up' crashed with
  KeyError. Pass the var to web; harden the leftover literal password in
  the 配置详解 snippet that contradicted the new guidance two lines down;
  blank line after the inserted sentence (bold heading merged into the
  paragraph); dev/prod table no longer claims dev uses 明文 passwords
- 11.8: FAQ still told readers to check passwords in .env after the same
  file banned passwords in .env -> point at secrets/db_password.txt;
  backup sidecar env vars updated to tiredofit/db-backup 4.x interface
  (DB01_* + DB01_PASS_FILE + DB01_BACKUP_INTERVAL - the unprefixed DB_*
  names are ignored by current :latest; verified against upstream README)
- demo/wordpress: compose now references secrets files that ship nowhere
  -> add README with the creation commands from 11.8; demo/django: align
  with the hardened 11.6 (env-injected password, passed to web too)
- 04_image multistage demos: go build without go.mod fails on module-mode
  Go (reproduced by reviewer on go1.26) -> add 'go mod init helloworld'
  matching the 7.17 doc pattern in all three Dockerfiles
- 21.7: init script reworked init-db.sql -> init-db.sh per the official
  image's env-reading .sh hook - removes the baked 'secure_password' AND
  the CREATE DATABASE myappdb collision with POSTGRES_DB that aborted
  first-boot init (ON_ERROR_STOP); compose passes APP_DB_PASSWORD;
  microservices init.sql mount annotated schema-only (POSTGRES_USER:
  appuser would collide with CREATE USER); Dockerfile-redis healthcheck
  now authenticates via REDISCLI_AUTH read from redis.conf (plain
  redis-cli ping gets NOAUTH against requirepass - same class as the
  compose sibling eb5e4397 already fixed); dev-container dev/dev creds
  annotated local-only
- 19.3: Grafana admin password 'admin' sat directly under the newly added
  security warning -> env-injected like the rest of the stack
This commit is contained in:
yeasy
2026-06-10 12:06:57 -07:00
parent eb5e4397e8
commit 0cfd55af7b
9 changed files with 81 additions and 46 deletions
+2 -1
View File
@@ -4,7 +4,8 @@ WORKDIR /go/src/github.com/go/helloworld/
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
RUN go mod init helloworld \
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:latest as prod
@@ -4,4 +4,5 @@ WORKDIR /go/src/github.com/go/helloworld
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
RUN go mod init helloworld \
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
@@ -6,7 +6,8 @@ WORKDIR /go/src/github.com/go/helloworld/
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . \
RUN go mod init helloworld \
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . \
&& cp /go/src/github.com/go/helloworld/app /root
WORKDIR /root/
+5 -2
View File
@@ -136,6 +136,8 @@ services:
db:
condition: service_healthy
environment:
# settings.py 直接读取 POSTGRES_PASSWORD,必须传入 web 容器
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}
DATABASE_URL: postgres://django_user:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}@db:5432/django_db
volumes:
@@ -153,7 +155,7 @@ db:
environment:
POSTGRES_DB: django_db # 创建的数据库名
POSTGRES_USER: django_user # 数据库用户
POSTGRES_PASSWORD: django_password # 数据库密码
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD} # 数据库密码经环境变量注入,不写明文
volumes:
- postgres_data:/var/lib/postgresql/data # 持久化数据
healthcheck: # 健康检查,确保数据库就绪
@@ -241,6 +243,7 @@ ALLOWED_HOSTS = ['*']
```
生产环境优先使用 Docker Compose secrets 或外部密钥管理即使是本地示例也不要把固定密码写进 Compose 文件或 Django 默认值中
**为什么 HOST `db` 而不是 `localhost`**
Docker Compose 各服务通过服务名相互访问Docker 内置的 DNS 会将 `db` 解析为 db 服务容器的 IP 地址这是 Docker Compose 的核心功能之一
@@ -330,7 +333,7 @@ $ sudo chown -R $USER:$USER .
|--------|---------|---------|
| **Web 服务器** | `runserver` | `gunicorn` + Nginx |
| **DEBUG** | `True` | `False` |
| **密码管理** | 明文写在配置 | 使用 Docker Secrets 环境变量 |
| **密码管理** | 环境变量注入如本节 `${POSTGRES_PASSWORD:?}` | 使用 Docker Secrets 外部密钥管理 |
| **Volume** | 挂载代码目录 | 代码直接 COPY 进镜像 |
| **ALLOWED_HOSTS** | `['*']` | 具体域名 |
+8 -8
View File
@@ -167,13 +167,13 @@ $ docker compose exec -T db sh -c 'tmp=$(mktemp) && printf "[client]\nuser=wordp
volumes:
- ./backups:/backup
environment:
- DB_TYPE=mysql
- DB_HOST=db
- DB_NAME=wordpress
- DB_USER=wordpress
# 选用支持从文件读取密码的备份镜像,或用自定义 entrypoint 从 secret 文件注入。
- DB_PASS_FILE=/run/secrets/db_password
- DB_DUMP_FREQ=1440 # 每天备份一次
# tiredofit/db-backup 4.x 起按 DB01_ 前缀配置备份任务,并原生支持 _FILE 读密
- DB01_TYPE=mysql
- DB01_HOST=db
- DB01_NAME=wordpress
- DB01_USER=wordpress
- DB01_PASS_FILE=/run/secrets/db_password
- DB01_BACKUP_INTERVAL=1440 # 每天备份一次(单位:分钟)
secrets:
- db_password
depends_on:
@@ -212,7 +212,7 @@ WordPress 支持 Redis 缓存以提高性能。
**现象**访问页面显示 Error establishing a database connection**排查**
1. 检查 `docker compose logs wordpress`
2. 确认 `.env` 中的密码与 YAML 文件引用一致
2. 确认 `secrets/db_password.txt` 的内容正确且与数据库初始化时使用的密码一致改密码后需要重建 db 数据卷
3. 确认 `WORDPRESS_DB_HOST` 也是 `db` (服务名)
4. MySQL 8.4 可能需要几秒钟启动WordPress 会自动重试稍等片刻即可
+4 -1
View File
@@ -4,7 +4,7 @@ services:
db:
image: postgres
environment:
POSTGRES_PASSWORD: 'postgres'
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}
web:
build: .
@@ -13,3 +13,6 @@ services:
- .:/code
ports:
- "8000:8000"
environment:
# 与书中 11.6 节一致settings.py 从环境变量读取数据库密码
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}
+18
View File
@@ -0,0 +1,18 @@
# WordPress Compose 示例
本示例使用 Docker Compose secrets 管理数据库密码启动前需要先创建密钥文件参见书中 11.8
```bash
mkdir -p secrets
printf '%s\n' 'somestrongrootpassword' > secrets/db_root_password.txt
printf '%s\n' 'somestronguserpassword' > secrets/db_password.txt
chmod 600 secrets/*.txt
```
然后启动
```bash
docker compose up -d
```
注意`secrets/` 目录不要提交到版本库生产环境应改用平台的密钥管理能力
@@ -229,7 +229,7 @@ services:
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:?set GRAFANA_ADMIN_PASSWORD}
- GF_INSTALL_PLUGINS=grafana-piechart-panel
volumes:
- grafana_data:/var/lib/grafana
+39 -31
View File
@@ -291,8 +291,8 @@ RUN apk add --no-cache \
pg-stat-monitor \
curl
# 复制初始化脚本
COPY init-db.sql /docker-entrypoint-initdb.d/
# 复制初始化脚本.sh 形式可从环境变量读取密码避免在 SQL 中写明文
COPY init-db.sh /docker-entrypoint-initdb.d/
COPY health-check.sh /
RUN chmod +x /health-check.sh
@@ -302,40 +302,42 @@ HEALTHCHECK --interval=10s --timeout=5s --start-period=40s --retries=3 \
EXPOSE 5432
```
**初始化脚本init-db.sql**
**初始化脚本init-db.sh**
```sql
-- 创建自定义用户
CREATE USER appuser WITH PASSWORD 'secure_password';
官方镜像会执行 `/docker-entrypoint-initdb.d/` 下的 `.sh` 脚本因此应用用户的密码可以从环境变量注入而不必写进 SQL数据库 `myappdb` 已由入口脚本按 `POSTGRES_DB` 创建初始化脚本中不要重复 `CREATE DATABASE`否则首次初始化会因冲突而中止
-- 创建数据库
CREATE DATABASE myappdb OWNER appuser;
```bash
#!/bin/bash
set -e
-- 创建扩展
\c myappdb
psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB" <<-EOSQL
-- 创建应用用户密码来自环境变量 APP_DB_PASSWORD
CREATE USER appuser WITH PASSWORD '$APP_DB_PASSWORD';
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS hstore;
CREATE EXTENSION IF NOT EXISTS pg_trgm;
-- 创建扩展
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS hstore;
CREATE EXTENSION IF NOT EXISTS pg_trgm;
-- 创建表
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
username VARCHAR(255) NOT NULL UNIQUE,
email VARCHAR(255) NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 创建表
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
username VARCHAR(255) NOT NULL UNIQUE,
email VARCHAR(255) NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 创建索引
CREATE INDEX idx_users_username ON users (username);
CREATE INDEX idx_users_email ON users (email);
-- 创建索引
CREATE INDEX idx_users_username ON users (username);
CREATE INDEX idx_users_email ON users (email);
-- 授予权限
GRANT CONNECT ON DATABASE myappdb TO appuser;
GRANT USAGE ON SCHEMA public TO appuser;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO appuser;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO appuser;
-- 授予权限
GRANT CONNECT ON DATABASE $POSTGRES_DB TO appuser;
GRANT USAGE ON SCHEMA public TO appuser;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO appuser;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO appuser;
EOSQL
```
**健康检查脚本health-check.sh**
@@ -363,6 +365,7 @@ services:
POSTGRES_DB: myappdb
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
APP_DB_PASSWORD: ${APP_DB_PASSWORD:?set APP_DB_PASSWORD in .env}
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=en_US.UTF-8"
volumes:
- postgres_data:/var/lib/postgresql/data
@@ -510,8 +513,10 @@ CMD ["redis-server", "/usr/local/etc/redis/redis.conf"]
EXPOSE 6379
# redis.conf 启用了 requirepass健康检查必须带认证否则只会收到 NOAUTH 错误
# REDISCLI_AUTH 可避免把密码出现在进程参数中
HEALTHCHECK --interval=5s --timeout=3s --retries=5 \
CMD redis-cli ping || exit 1
CMD sh -c 'REDISCLI_AUTH="$(awk "/^requirepass /{print \$2}" /usr/local/etc/redis/redis.conf)" redis-cli ping | grep -q PONG'
```
**redis.conf 配置**
@@ -522,7 +527,7 @@ bind 0.0.0.0
# 端口
port 6379
# 密码保护
# 密码保护示例占位值部署时应替换并避免提交到版本库
requirepass your_secure_password
# 内存管理
@@ -621,6 +626,8 @@ services:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
# 此处的 init.sql 只放建表/索引等 schema 语句
# 用户与数据库已由 POSTGRES_USER/POSTGRES_DB 创建脚本中不要重复 CREATE USER/DATABASE
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- backend-network
@@ -844,6 +851,7 @@ services:
postgres:
image: postgres:16-alpine
environment:
# 仅限本机 Dev Container 的一次性开发凭证不要在任何共享/联网环境复用
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev
POSTGRES_DB: myapp