软件工程界流传最广的笑话,大概是这句:"在我电脑上是好的。"本机 Node 20,同事 Node 16,服务器还是某个上古版本——直到 Docker 出现,这句话才终于有了终结的方法。
镜像与容器:一张光盘和一台装好的电脑
概念先掰扯清楚,其实就两层:
- 镜像(Image):一个只读的"环境模板",类似装机光盘;
- 容器(Container):用镜像跑起来的实例,类似用这张光盘装好的一台电脑,可以开很多台,互相隔离。
跑一个最简单的例子,一行命令起一个 nginx:
# 拉取镜像(不指定 tag 默认 latest)
docker pull nginx
# 起一个容器:命名 web,把宿主机 8080 映射到容器 80 端口
docker run -d --name web -p 8080:80 nginx
# 打开 http://localhost:8080 —— 欢迎页就在那等着你
注意 -p 8080:80:左边是宿主机端口,右边是容器端口。整个过程没装 nginx、没改系统配置,删掉容器环境就干干净净。
写你的第一个 Dockerfile
镜像从哪来?官方仓库固然香,但自己的应用还得自己打包。假设有个 Node 服务,Dockerfile 长这样:
FROM node:20-alpine # 基础镜像:越小越香
WORKDIR /app # 容器内工作目录
COPY package*.json ./ # 先只拷贝依赖清单
RUN npm ci --omit=dev # 装依赖(可被缓存!)
COPY . . # 再拷贝源码
EXPOSE 3000
CMD ["node", "server.js"]
两个细节值得记住:先拷贝依赖文件再拷源码,是因为 Docker 的分层缓存——源码天天改,依赖很少动,这样改代码后重建镜像不会重新装依赖。另一个是选 alpine 变体,镜像体积能小一个数量级。
# 构建镜像并运行
docker build -t myapp .
docker run -d -p 3000:3000 myapp
docker-compose:一键起飞
真实项目往往不止一个容器:应用 + 数据库 + 缓存……命令行一个个 run 会疯掉。把它们写进 docker-compose.yml:
services:
web:
build: .
ports: ["3000:3000"]
depends_on: [db]
environment:
DATABASE_URL: postgres://wang:wang@db:5432/blog
db:
image: postgres:16
environment:
POSTGRES_USER: wang
POSTGRES_PASSWORD: wang
POSTGRES_DB: blog
volumes: ["pgdata:/var/lib/postgresql/data"]
volumes:
pgdata:
然后只需要一句咒语:
docker compose up -d # 后台启动整套服务
docker compose logs -f web # 盯着应用日志
docker compose down # 全部撤收(-v 连数据卷一起删)
注意容器之间直接用服务名互访(比如 db:5432),compose 会自动搞定内部 DNS 和网络。
常用命令速查
| 命令 | 作用 |
|---|---|
docker ps -a | 查看所有容器(含已停止) |
docker images | 列出本地镜像 |
docker logs -f <容器> | 跟踪容器日志 |
docker exec -it <容器> sh | 钻进容器内部排查 |
docker system prune | 清理闲置镜像/容器,回收磁盘 |
新手最常见的坑:
docker run忘了-p端口映射,然后纳闷"服务明明活着怎么访问不了"。先看映射,再怀疑人生。
把环境装进集装箱之后,你会发现部署、迁移、回滚都变成了复制粘贴级别的工作。下一篇折腾记,我会聊聊怎么用 Docker 把一台旧笔记本变成家庭实验室——毕竟这可是"折腾"分类的招牌节目。🐾