Docker入门:用一个小例子理解镜像、容器和Dockerfile

举报
yd_289430518 发表于 2026/10/29 19:36:37 2026/10/29
【摘要】 很多人第一次用 Docker,是因为部署说明写着「先装 Docker,再执行一条启动命令」。命令能跑通,镜像、容器、Dockerfile 却经常搅在一起:改了一行代码,不知道该重新构建还是只要重启;删掉容器,又担心镜像和数据一起消失。先把这三个词拆开,后面看日志、对端口、写编排文件会顺很多。 三个概念,对应三样东西镜像是一份只读的打包结果。它把基础系统、语言运行时和你的程序按层叠在一起。同一...

很多人第一次用 Docker,是因为部署说明写着「先装 Docker,再执行一条启动命令」。命令能跑通,镜像、容器、Dockerfile 却经常搅在一起:改了一行代码,不知道该重新构建还是只要重启;删掉容器,又担心镜像和数据一起消失。先把这三个词拆开,后面看日志、对端口、写编排文件会顺很多。

三个概念,对应三样东西

镜像是一份只读的打包结果。它把基础系统、语言运行时和你的程序按层叠在一起。同一份镜像在另一台机器上拉下来,启动出来的环境应当一致。镜像本身不跑进程,也不能当成正在运行的环境去改文件。

容器是镜像的一次运行实例。可以把它理解成:用这份镜像启动起来的进程,加上一层只属于这次运行的可写层。同一个镜像可以起多个容器,它们互不影响。删掉一个容器,镜像还在,其他容器也不受影响。写在可写层里的文件会随容器删除而消失,需要留下来的数据应放到卷或外部存储,而不是指望容器自己的磁盘。

Dockerfile 是构建镜像的配方,不是运行时配置。FROM、WORKDIR、COPY、CMD 这些指令在 docker build 时执行,产出镜像;docker run 才根据镜像创建容器。端口映射、临时环境变量属于运行参数,写在 docker run 或 Compose 里,不要和构建配方混在一起。

一个能跑的最小例子

在空目录放两个文件。app.py 只用标准库起 HTTP 服务。监听地址必须是 0.0.0.0,如果写成 127.0.0.1,进程只接受容器内部的连接,宿主机即使做了端口映射也访问不到。

from http.server import BaseHTTPRequestHandler, HTTPServer

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        body = b"hello from container\n"
        self.send_response(200)
        self.send_header("Content-Type", "text/plain; charset=utf-8")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

if __name__ == "__main__":
    HTTPServer(("0.0.0.0", 8080), Handler).serve_forever()

同目录的 Dockerfile:

FROM python:3.12-alpine
WORKDIR /app
COPY app.py .
EXPOSE 8080
CMD ["python", "app.py"]

python:3.12-alpine 体积小,并固定到 3.12 这条标签,避免以后 latest 悄悄换掉解释器。CMD 用 JSON 数组,让 python 自己成为 1 号进程,而不是套一层 shell。EXPOSE 只是声明容器内端口,不会自动映射到宿主机。

在该目录执行:

docker build -t hello-mini .
docker run --rm -p 8080:8080 hello-mini

另开一个终端执行 curl http://127.0.0.1:8080,应当看到 hello from container。-p 8080:8080 左边是宿主机端口,右边是容器内端口。--rm 表示容器退出后自动删除,适合这次试验;镜像 hello-mini 仍会留在本地。

常见坑

改了 app.py 却只执行 docker restart,页面不会变。COPY 发生在构建阶段,源码已经打进镜像层,必须重新 docker build -t hello-mini .,再用新镜像启动容器。

只写了 EXPOSE 8080 却忘了 -p,容器里的服务在宿主机上没有入口。反过来,映射了端口但进程监听 127.0.0.1,结果同样是连接被拒绝。

基础镜像写成 latest,今天能构建、下周同一份 Dockerfile 可能拉到另一套系统库。示例里固定 3.12-alpine,就是为了让这次构建可复现。

把数据库文件或上传目录放在容器可写层里,容器一删数据就没了。这个小例子没有状态,真正跑服务时要把数据目录挂到卷上。

小结

镜像是构建产物,容器是它的一次运行,Dockerfile 只负责把镜像做出来。对照这个例子记住三件事:改代码要重新 build,对外端口看 -p 而不是只看 EXPOSE,需要保留的数据不要只放在容器里。这三件事分清之后,再去读 Compose 或云上的容器服务,就不会把「构建」和「运行」当成同一步。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。