Dockerfile 第一课:把自己的网站打包成镜像
用别人的镜像很爽,但自己的网站怎么容器化?小够教你写第一个 Dockerfile:FROM、COPY、RUN、CMD 四个核心指令,外加 .dockerignore、多阶段构建和五个新手坑。
我是小够,只推够用的 VPS。
前两篇我们用的都是别人的镜像。今天反过来:把自己的网站打包成镜像,换台 VPS 一条命令就能跑起来。这就是 Dockerfile 干的事——一份"菜谱",告诉 Docker 镜像怎么做出来。
准备一个网站
假设你有个静态网站,目录结构:
my-site/
├── index.html
└── style.css
写 Dockerfile
在 my-site/ 里新建文件 Dockerfile(没后缀,就叫这个名):
FROM nginx:alpine
COPY . /usr/share/nginx/html
就两行。FROM 是底子(用轻量 Nginx 镜像),COPY 把当前目录所有文件拷进容器的网站目录。
构建:
docker build -t my-site:v1 .
-t 是起名字(仓库名:标签),最后的 . 表示 Dockerfile 在当前目录。构建完就能跑:
docker run -d -p 8080:80 my-site:v1
四个核心指令
| 指令 | 干嘛的 |
|---|---|
FROM | 基础镜像,每份 Dockerfile 第一行 |
COPY | 把文件拷进镜像 |
RUN | 构建时执行命令(如装依赖) |
CMD | 容器启动时执行的命令 |
比如一个 Node 项目:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
CMD ["node", "server.js"]
WORKDIR:容器里的工作目录,后面命令都在这执行,不存在会自动建。COPY package*.json ./先拷依赖清单再RUN npm install:这样只有依赖变了才重装,构建更快——这是利用了镜像分层缓存。CMD:容器启动时执行的命令,一个镜像只能有一个生效的 CMD,写多个只有最后一个有用。
COPY 和 ADD 别搞混
COPY 就是单纯拷文件,ADD 还能自动解压 tar 包、支持远程 URL。听起来 ADD 更强,但正因为它"顺手做了太多事",行为不好预测。小够的建议:默认用 COPY,需要解压再考虑 ADD。这是官方最佳实践里的原话。
EXPOSE 写不写都行,但写了好
EXPOSE 80
EXPOSE 只是声明"这个容器用 80 端口",是个文档作用,不写端口映射照样能用(-p 照样生效)。但写了之后,docker ps 的 PORTS 列信息更全,别人看你的 Dockerfile 也知道要映射哪个端口。好习惯,顺手写。
CMD vs ENTRYPOINT
# CMD:会被 docker run 后面的参数覆盖
CMD ["node", "server.js"]
# docker run myapp --inspect → 实际执行 "--inspect",CMD 被替换了
# ENTRYPOINT:不会被覆盖,run 后面的参数会追加
ENTRYPOINT ["node", "server.js"]
# docker run myapp --inspect → 实际执行 "node server.js --inspect"
个人项目用 CMD 就够了。ENTRYPOINT 适合做"工具型"镜像(比如把某个 CLI 打包成镜像),知道有这个区别就行。
.dockerignore 别忘了
跟 .gitignore 一个思路,构建时排除不需要的文件:
node_modules
.git
*.log
.env
不写的话,本地几百 MB 的 node_modules 也会被拷进构建上下文,又慢又大。.env 尤其不能进镜像——密码打进镜像,镜像传到哪密码跟到哪。
进阶:多阶段构建(知道就行)
# 第一阶段:构建
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# 第二阶段:只拿构建产物
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
最终镜像里只有 Nginx 和静态文件,没有 Node、没有源码、没有 node_modules,体积小一大截,攻击面也小。前端项目、Go 项目都适合这么玩。刚开始不用,等镜像体积让你心疼了再上。
五个新手常踩的坑
第一,标签别总用 latest。 my-site:v1、my-site:v2 这样打版本,出问题能回滚。全用 latest 的话,哪天镜像变了你都不知道。
第二,每条 RUN 尽量合并。 Dockerfile 里每条 RUN 是一层,层数太多镜像臃肿:
# 不好:三层
RUN apt update
RUN apt install -y curl
RUN rm -rf /var/lib/apt/lists/*
# 好:一层
RUN apt update && apt install -y curl && rm -rf /var/lib/apt/lists/*
第三,构建上下文别放乱七八糟。 docker build 会先把整个目录发给 Docker 守护进程,目录里有个几个 GB 的备份包,构建慢死。配合 .dockerignore,或者单独建个干净的构建目录。
第四,镜像传到哪。 自己玩的话,docker save 导出 tar 包拷到新机器 docker load 就行:
docker save my-site:v1 -o my-site-v1.tar
团队用再考虑搭私有仓库,个人用户没必要一上来就搞。
第五,构建慢先看是不是网络问题。 RUN apt update 卡住,多半是源慢,换国内源(Debian/Ubuntu 换源前面月份讲过)。npm install 慢就换国内 registry。别一上来就怀疑人生。
够用就好
Dockerfile 不用学全,80% 的场景就是上面这些。先把自己的网站容器化跑起来,遇到具体问题再查文档——小够也是这么过来的。