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 或云上的容器服务,就不会把「构建」和「运行」当成同一步。
- 点赞
- 收藏
- 关注作者
评论(0)