每次代码提交后,都需要等待长达15分钟的流水线构建,原因仅仅是Docker镜像在重复下载海量依赖。这曾是我的日常。每一次提交、特性分支或错误修复,构建过程都异常缓慢,报错信息晦涩难懂,容器本身也臃肿不堪,仿佛在与一个不透明的黑盒子作斗争。
而这一切,只是为了运行一个 Node.js 应用,加上 Redis 和 PostgreSQL。这并不是多么复杂的架构。
我曾坚信使用Docker是“正确的事”——它承诺环境隔离、生产环境一致性以及便捷的扩展性。但实际带来的,往往是陡增的复杂度、无尽的YAML配置以及对本地环境频繁故障的排查。直到有一天,我做出了一个决定:彻底从工作流中移除Docker。
结果出乎意料:世界并未崩塌,反而变得轻松,是一种“为何不早点尝试”的轻松。
替代Docker的方案及其优势
一个被忽视的事实是:多数开发者并非需要Docker本身,而是需要可复现的环境与清晰的文档。
因此,我逐一审视并替换了Docker的每个用途,过程如同拔除一颗困扰已久的智齿,结果令人舒畅。
1. 本地开发环境:使用 direnv + asdf + 脚本
对于我的单体仓库(Monorepo)项目——包含React前端、Node.js API、PostgreSQL和Redis——Docker显得过度设计。
现在的方案是:
- asdf:为项目锁定Node.js、PostgreSQL等运行时的具体版本。
- direnv:进入项目目录自动加载相应的环境变量。
- 统一的
dev.sh 脚本:一键启动所有服务(通过Homebrew启动PostgreSQL,用brew services管理Redis,应用层直接npm start)。
成效显著:近乎零开销,无需上下文切换,服务启动迅速,开发体验流畅。
2. CI/CD 构建:原生Runner与简洁脚本
以往在GitHub Actions中使用Docker,镜像日益臃肿,一个微服务的构建耗时长达12分钟。
现在的优化策略:
- 使用 GitHub托管的Linux Runner(预装Node.js等环境)。
- 编写干净的 Shell脚本 执行
npm install、代码检查与测试。
- 彻底避免 Docker-in-Docker 的复杂性与缓存失效问题。
结果是:CI构建时间稳定在3分钟以内,可靠性大幅提升。
3. 部署:采用无需Dockerfile的现代平台
曾为部署编写Dockerfile,从Heroku到Kubernetes,投入了大量精力。如今:
- Node.js应用部署在 Fly.io:支持直接从源码构建。
- 边缘函数使用 Deno Deploy:完全无需容器概念。
- 数据库采用 Supabase 或 Neon 等全托管的PostgreSQL服务。
一切回归到那种朴素而可靠的状态:它确实能正常工作。
关于“生产环境一致性”的思考
“Docker能保证开发与生产环境一致!”——这个观点我已听过无数遍。
但现实是,除非你极其严格地锁定每一层镜像版本并频繁重建,否则容器环境同样会悄然发生漂移。我曾遭遇容器在本地运行良好,却在预发布环境崩溃的情况,根源常是基础镜像更新或Alpine软件包变动。
改用源码构建配合轻量化工具链后,与环境一致性相关的问题反而减少了。原因在于:
- 系统组件更少,更透明。
- 依赖版本清晰明确。
- 本地环境与生产环境的差异更容易理解和控制。
问题的本质:是对自身环境缺乏信任
Docker常常成为一种“拐杖”,用以掩盖以下流程问题:
- 文档不完善。
- 团队成员本地环境配置五花八门(“雪花环境”)。
- 新人上手成本高,如同闯关。
当我弃用Docker,被迫完成了三件事:
- 编写清晰的环境搭建文档。
- 严格锁定所有工具版本。
- 创建易于理解和执行的启动脚本。
结果?团队 onboarding 速度更快,问题排查更直接,对部署也更有信心。
弃用Docker带来的实际收益
- 构建时间:从 12 分钟降至 3 分钟。
- 新成员上手时间:从 2 小时缩短至 15 分钟(执行脚本即可)。
- 本地存储占用:从超过20GB的Docker volumes减少到500MB以内。
- 心智负担:技术栈变得清晰可理解,告别了容器间网络连通的“玄学”问题。
给CRUD应用上K8s?你可能不需要
Docker和Kubernetes听起来很酷,但需要理性评估。如果你构建的是一个典型的Web应用加数据库,核心需求可能只是一台服务器和定时任务,而非一个完整的容器编排集群。
许多开发者陷入了一种误区:将工具的复杂度等同于工程的高级感。但真正的智慧在于用简单的方案解决实际问题。不必要的容器化只会让架构更沉重、更难解释和维护。
何时Docker依然有价值
当然,Docker并非一无是处,它在以下场景中作用关键:
- 需要操作系统级别的安全沙箱隔离。
- 项目涉及多语言、多依赖的混合环境(如Go后端与Python机器学习服务)。
- 基础设施团队需要构建标准化的生产镜像体系。
- 处理遗留项目,解决复杂的依赖冲突问题。
- 使用像Playwright这类需要在特定容器环境中运行无头浏览器的工具链。
但对于大多数使用Node.js、Rails、Django或Laravel的常规Web应用,我的建议是:优先追求简单、清晰和可运行性。
常见问题解答(FAQ)
Q:开发环境真的可以不用Docker吗?
A:在大多数情况下可以。使用 asdf、nvm、pyenv 等版本管理工具配合自动化脚本,能覆盖Docker 90%的优势,且更轻量、透明。Docker应被视为一个可选项,而非必选项。
Q:团队协作不是更需要Docker来统一环境吗?
A:恰恰相反,当你的文档和流程足够清晰时,统一的版本和脚本能使团队协作更高效。Docker通常在流程文档不完善时被用作补救措施。
Q:不用Docker如何部署?
A:完全可以。现代平台如 Fly.io、Railway、Vercel、Deno Deploy 都支持直接从源代码部署,容器并非必需步骤。这些平台简化了云原生应用的交付流程。
Q:替代Docker的工具链是什么?
A:可以组合使用以下工具:
- 版本管理:asdf / nvm / pyenv
- 环境变量:direnv(自动加载)
- 自动化:可复现的Shell脚本
- 本地服务:brew / apt 安装数据库等
- 部署:支持源码部署的现代平台
提升效率的工具推荐
核心结论是:你并非一定需要容器。你更需要的是清晰的流程、严格的规范,以及一套自己能够透彻理解的技术栈。
停止为了迎合DevOps的“潮流”而优化,转而为开发速度、心智清晰度和项目交付能力而优化。