ZPMan10197/docker-hello

GitHub: ZPMan10197/docker-hello

这是一个用于学习Docker容器化和云安全基础的Python web应用示例,通过Docker Compose部署并集成CVE扫描,以实践安全左移理念。

Stars: 1 | Forks: 0

# docker-你好 [![CI](https://static.pigsec.cn/wp-content/uploads/repos/2026/05/c4b5413db3071438.svg)](https://github.com/ZPMan10197/docker-hello/actions/workflows/ci.yml) 我的第一个 Docker 项目。这是一个最小化的 Python HTTP 服务器,打包成一个容器,作为我云安全工程作品集的第一步构建。整个过程中我使用 Claude Code 作为结对编程伙伴;所有设计决策均由我做出。 ## 功能介绍 通过 Docker Compose 运行一个 Python HTTP 服务器和一个 Redis 实例。当你访问服务器时,它会返回容器的主机名和一个存储在 Redis 中的持久化访问计数器。 ## 运行方法 ``` docker compose up --build ``` 然后在另一个终端: ``` curl http://localhost:8000 ``` 或在浏览器中打开 `http://localhost:8000`。 使用 `Ctrl + C` 停止,然后执行 `docker compose down` 移除容器。 ## 我学到的知识 - **镜像 vs 容器** — 镜像是一个不可变的模板;每次 `docker run` 都会创建一个新的容器实例。相同的镜像,每次运行时主机名不同。 - **Dockerfile 基础** — `FROM` 设置基础镜像,`WORKDIR` 设置容器内的工作目录,`COPY` 在构建时将文件打包进镜像,`CMD` 定义默认的启动命令。 - **端口映射** — 在 `docker-compose.yml` 中,`ports` 键映射 `HOST:CONTAINER`,并在运行 `docker compose up` 时自动发布端口。没有它,内部的服务器从外部无法访问。 - **`EXPOSE` 仅用于文档** — 实际的端口发布是通过 `docker-compose.yml` 中的 `ports` 键实现的,而不是 Dockerfile 中的 `EXPOSE`。 - **在容器内绑定到 `0.0.0.0`** — 这是必需的,以便服务器能够接收通过 Docker 端口转发路由进来的流量。绑定到 `127.0.0.1` 将使其从容器外部无法访问。 - **容器自带运行时** — 运行我代码的 Python 解释器位于容器内部,而不是在我的 Mac 上。这正是容器能在不同环境间移植的原因。 - **CI/CD 流水线** — GitHub Actions 在每次推送时都会启动一个新的 Ubuntu 运行器,构建镜像,并运行 Trivy 扫描 CVE。如果发现严重漏洞,构建会自动失败,防止任何发布。 - **密钥扫描** — Gitleaks 在每次推送时扫描完整的 git 历史,查找意外提交的凭证(API 密钥、令牌、密码)。它在 Docker 构建之前运行,因此泄露的密钥会立即导致流水线失败。 - **CVE 扫描** — Trivy 根据已知漏洞数据库检查镜像中的每个软件包。设置 `ignore-unfixed: true` 可以避免因没有可用补丁的漏洞而产生的干扰。 - **多容器网络** — Docker Compose 将服务放置在一个共享的私有网络上。容器通过服务名称(例如 `redis`)互相查找,而不是通过 IP。Docker 的内部 DNS 会自动解析名称。 - **无状态应用,有状态数据存储** — Python 服务器本身不保存任何状态。访问计数器存在于 Redis 中。重启应用容器不会重置计数,因为数据存储在单独的容器中。 ## 后续计划 - 重构 Redis 连接以使用环境变量(目前为 Compose 硬编码) - 添加单元测试并在 CI 的构建步骤之前运行它们 - 使用 Cosign 添加镜像签名并在工作流中验证签名 - 推送到 AWS ECR 并在 ECS Fargate 上部署 - 使用 Terraform 将 AWS 基础设施代码化 - 添加 CloudWatch 日志记录和用于可疑行为的 GuardDuty 警报 ## 技术栈 - Python 3.12 (slim) - Redis 7 (Alpine) - Docker Compose - Apple Silicon (M1) 上的 Docker Desktop ## 设计决策 **为什么选择 `python:3.12-slim` 作为基础镜像?** 与 `python:3.12` 相比,攻击面更小,拉取速度更快。生产环境会考虑使用 alpine 或 distroless。 **为什么是 `0.0.0.0` 而不是 `127.0.0.1`?** 在容器内部,绑定到 `127.0.0.1` 只监听回环接口——Docker 转发的流量到达 `eth0` 时会被拒绝。对于容器来说,`0.0.0.0` 是正确的;安全边界是端口发布规则,而不是绑定地址。 **为什么 Dockerfile 中有 `EXPOSE`?** 是为阅读 Dockerfile 的人提供的文档。实际的端口发布是通过 `docker-compose.yml` 中的 `ports` 键实现的。`EXPOSE` 对网络没有任何作用。 **为什么将基础镜像固定到 sha256 摘要?** 像 `python:3.12-slim` 这样的标签是可变的——它们指向的镜像随时可能更改。sha256 摘要是对确切镜像字节的加密指纹,因此每次构建都保证使用相同的镜像。这可以防止意外的上游更改和供应链攻击。 **为什么以非 root 用户身份运行?** 默认情况下,容器进程以 root 身份运行。如果攻击者利用了应用程序,他们将在容器内获得 root 权限。创建一个非特权用户(`appuser`)并使用 `USER` 指令切换到该用户,可以限制任何入侵的影响范围。 **为什么添加 HEALTHCHECK?** Docker 无法判断你的应用是否真的在工作——只能判断进程是否在运行。`HEALTHCHECK` 每 30 秒运行一次真实的 HTTP 请求。如果失败三次,Docker 会将容器标记为不健康,允许编排器(ECS、Kubernetes)自动重启它。 **为什么在 CI 中使用 Trivy 而不仅仅在本地运行?** 在本地运行扫描很容易忘记或跳过。将其放入流水线使其成为自动且强制性的——每次推送都会被扫描,无一例外。这就是"左移"安全原则:在构建时捕获漏洞,防止其到达生产环境。 **为什么在 Dockerfile 中运行 `apt-get upgrade`?** 基础镜像(`python:3.12-slim`)由其维护者定期重建,但并非总是在 Debian 补丁发布后立即更新。在构建时运行 `apt-get upgrade` 会拉取任何尚未包含在基础镜像中的可用修复。`rm -rf /var/lib/apt/lists/*` 会清理软件包索引缓存,以避免它使镜像层膨胀。 **为什么添加 Gitleaks 密钥扫描?** 意外提交的凭证是导致云泄露的最常见原因之一——公共仓库中的 AWS 密钥可能在几分钟内被发现和滥用。Gitleaks 在每次推送时扫描完整的 git 历史(不仅仅是最新提交),因此即使是一个提交后又在后续提交中"删除"的密钥也会被发现。在流水线中首先运行它意味着可以快速失败,避免浪费时间在构建上。 **为什么 Redis 是一个单独的容器,而不仅仅是一个 Python 库?** `import redis` 是客户端——它是知道如何与 Redis 通信的代码。Redis 数据库是一个必须在某处运行的独立程序。将其保留在自己的容器中符合生产系统的工作方式:无状态的应用层,独立的数据层。这也意味着你可以在不丢失数据的情况下重启应用。
标签:Docker, Docker Compose, HTTP服务器, NIDS, Python, Redis, Web截图, 安全工程, 安全扫描, 安全防御评估, 容器化, 容器安全, 搜索引擎查询, 无后门, 时序注入, 版权保护, 端口映射, 访问计数器, 请求拦截, 逆向工具, 镜像构建