news 2026/9/12 21:50:56

Docker 入门:镜像、容器、Compose 一次讲透

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Docker 入门:镜像、容器、Compose 一次讲透

Docker 入门:镜像、容器、Compose 一次讲透

引言

装好 Docker 之后,下一步就是搞懂它到底在干嘛。不少新手卡在"会用命令但不懂概念":docker run会敲,问"镜像和容器有什么区别"就答不上来。概念不清,后面的 Dockerfile、Compose 全学不踏实,遇到问题也不知道从哪查。

这篇文章从最核心的三个概念讲起,用类比把话说透;接着过一遍高频命令;然后手把手写一个完整的 Dockerfile 和一个 compose 文件,都是能直接跑起来的;最后聊几句生产环境的心得。全程不要求你有基础,照着敲就行。

核心概念:镜像、容器、卷、网络

先用大白话定个基调:镜像像"类",容器像"实例"。你写过 Python 就知道,类可以 new 出无数个对象;镜像同理,build 一次,可以 run 出无数个容器。对象之间互相隔离,容器之间也隔离。

概念类比一句话解释
镜像 Image类的定义 / 安装包只读模板,包含代码、运行时、依赖、配置
容器 Container类的实例 / 正在运行的程序镜像跑起来之后的进程,可启停、可删除
卷 VolumeU 盘容器外的持久化存储,容器删了数据还在
网络 Network虚拟局域网容器之间通信的通道,可自定义隔离
仓库 Registry应用商店存放镜像的地方,官方的是 Docker Hub

三个细节单独强调:

  • 镜像是分层的。一个镜像由多层只读层叠加而成,拉取时公共层可以复用。多个镜像共用同一个基础层时,体积小、启动快,这就是为什么docker pull有时"秒完成"——那几层早就有了。
  • 容器是无状态的。容器里的文件系统默认是临时的,容器一删,里面改的东西全没。要持久化,就必须挂卷。这个特性常被新手忽略,数据丢一次就记住了。
  • 镜像不可改,只能叠加。想改镜像不是直接编辑,而是写 Dockerfile 重新构建出新的镜像。所以"改镜像"在 Docker 世界里等于"重新 build"。

再补一个理解上的关键点:Docker 不是虚拟机。虚拟机里跑着完整的操作系统,开销大、启动慢;容器共享宿主机内核,只有进程级别的隔离,所以秒级启动、资源占用低。代价是隔离性不如虚拟机——这也是生产环境容器安全要单独关注的原因。想明白这个区别,你判断"容器还是虚拟机"就有依据了。

常用命令:这套够你用很久

命令不用全背,下面这套日常开发足够,先复制收藏。

# ---- 镜像管理 ----dockerimages# 列出本机镜像dockerpull nginx# 拉取镜像(不指定 tag 默认 latest)dockerrmi nginx# 删除镜像# ---- 容器生命周期 ----dockerrun-d--nameweb-p8080:80 nginx# 后台运行,容器名 web,宿主机 8080 映射到容器 80dockerps# 查看运行中的容器dockerps-a# 查看所有容器(含已停止)dockerstop web# 停止容器dockerstart web# 启动已停止的容器dockerrmweb# 删除容器(需先 stop)dockerrm-fweb# 强制删除运行中的容器# ---- 日志与进入 ----dockerlogs web# 查看容器日志dockerlogs-fweb# 实时跟踪日志(Ctrl+C 退出)dockerexec-itwebbash# 进入容器内部终端dockerexecwebls/usr/share/nginx/html# 在容器内执行单条命令# ---- 清理 ----dockersystemdf# 看磁盘占用dockersystem prune# 清理悬空镜像、停止的容器等(谨慎,会删东西)

几个容易记混的参数说清楚:-d后台运行,--name起名字,-p 宿主机端口:容器端口端口映射,-it交互式终端。docker run负责"创建并启动",之后再启停用docker start / stop,别反复 run,否则会创建出多个容器。

再给三个高频补充命令:docker cp web:/app/log.txt ./把容器里的文件拷到宿主机;docker top web看容器里的进程;docker inspect web看容器的完整配置,IP、挂载、环境变量都在里面。docker inspect是新手的宝藏命令,很多"怎么查不到"的问题,答案都在它输出的 JSON 里。

最后提醒一句:docker system prune会连带删除所有已停止的容器、悬空镜像和未使用的网络,执行前确认没有需要保留的东西。想精准清理,加--filter条件,或直接按镜像 ID 手动删除。

写一个 Dockerfile:从零构建一个 Web 服务

光看命令不过瘾,直接写一个能跑的。场景:一个极简的 Python Web 服务,用 Flask 返回一句话。

先建项目目录和源码:

mkdirmyapp&&cdmyapp

app.py:

fromflaskimportFlask app=Flask(__name__)@app.route("/")defindex():return"Hello from Docker!"if__name__=="__main__":app.run(host="0.0.0.0",port=5000)

requirements.txt:

flask==3.0.3

Dockerfile(关键文件,每一行都注释了作用):

# 基础镜像:python 3.12 精简版 FROM python:3.12-slim # 设置工作目录,后续命令都在这个目录下执行 WORKDIR /app # 先复制依赖清单并安装,利用镜像分层缓存,改代码时不用重装依赖 COPY requirements.txt . RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple # 复制源码 COPY app.py . # 声明容器监听端口(仅说明作用,真正映射靠 -p) EXPOSE 5000 # 容器启动时执行的命令 CMD ["python", "app.py"]

构建并运行:

# 构建镜像,-t 是命名,末尾的 . 是构建上下文目录dockerbuild-tmyapp.# 预期输出最后两行:# Successfully built <镜像ID># Successfully tagged myapp:latest# 后台运行,映射端口dockerrun-d--namemyapp-p5000:5000 myapp# 验证服务curlhttp://localhost:5000# 预期输出:# Hello from Docker!# 看日志dockerlogs myapp

【此处需补真实截图】

常见翻车点:docker run之后curl连不上。先docker ps确认容器在跑,再docker logs看报错。新手最常见的错误是端口映射写反了——-p 5000:5000左边是宿主机端口,右边是容器端口,别倒过来。另外改完代码要重新docker build,容器里跑的是旧镜像的代码,这个也经常有人忽略。

docker-compose 实战:一个命令起整套服务

单容器够用,但真实项目往往是"Web + 数据库"起步。Compose 的价值就在这:一份 YAML 描述多容器,一个命令全部起。

docker-compose.yml(放在项目根目录):

services:web:build:.ports:-"5000:5000"depends_on:-dbenvironment:# 应用代码里读这个环境变量连接数据库DB_HOST:dbdb:image:mysql:8.0environment:MYSQL_ROOT_PASSWORD:root123MYSQL_DATABASE:myapp# 数据持久化:MySQL 的数据落到命名卷里volumes:-db_data:/var/lib/mysqlvolumes:db_data:

注意两点:build: .表示 web 这个服务用当前目录的 Dockerfile 构建;db直接拉现成的 mysql 镜像。两个服务在同一个默认网络里,靠服务名互相访问——web 里连数据库,主机填db而不是 localhost,这是新手最容易懵的地方。

启动与验证:

# 后台启动所有服务dockercompose up-d# 预期输出:# [+] Running 3/3# ✔ Container myapp-web-1 Started# ✔ Container myapp-db-1 Started# 看服务状态dockercomposeps# 实时看某个服务的日志dockercompose logs-fweb# 停止但保留数据dockercompose down# 全部清理(连同卷一起删,慎用)dockercompose down-v

【此处需补真实截图】

docker compose up -d跑完,web 和 db 都起来了。web 里如果有连数据库的代码,通过环境变量 DB_HOST 指向 db 服务即可。验证仍然用curl http://localhost:5000。注意新版语法是docker compose(空格),老版才是docker-compose(短横线),两条命令别混。

compose 排错三板斧:docker compose ps看状态,docker compose logs看日志,docker compose config验证 YAML 语法。最后一条尤其好用,缩进写错时它能直接指出问题位置,不用对着文档猜。

生产环境注意点

本地跑通和线上稳是两个世界,几个经验直接给:

  • 别用 latest 标签。不写 tag 就默认 latest,每次构建覆盖,部署时根本不知道线上跑的是哪一版。构建时固定版本号,比如myapp:20260826-v1
  • 别用 root 跑服务。基础镜像默认 root,容器一旦被攻破就拿到 root 权限。Dockerfile 里建普通用户再切换,成本很低。
  • 多用多阶段构建。编译型语言(Go、Java)先构建再运行,最终镜像只留运行产物,体积能缩一个数量级,拉取和启动都快。
  • 数据必须落卷。任何有状态的服务(数据库、缓存、上传文件)都要挂 volume,容器说换就换,数据不能跟着没。数据库实例最好连绑定挂载都用上。
  • 日志别只靠 docker logs。容器一重启,docker logs里之前的日志就翻页了。生产环境把 stdout 日志接到集中式日志平台。
  • 基础镜像定期升级。基础镜像的补丁滞后等于裸奔。配合镜像仓库的漏洞扫描,把修复计划排进迭代。
  • 加健康检查docker run--health-cmd或 compose 里的healthcheck让 Docker 定期探测服务是否存活,编排系统靠它做自动重启和摘除流量。本地可能用不上,一旦上了编排就离不开。
  • 写 .dockerignore。构建上下文默认把整个目录打包发给 Docker,node_modules、.git、pycache这些动辄上百 MB 的东西全传过去,构建又慢又脏。在项目根目录写一个 .dockerignore,规则类似 .gitignore,把不该进镜像的都排除掉。

还有一个共识:一切配置用环境变量或配置文件注入,别把密码、密钥写进 Dockerfile。镜像一旦被推送出去,里面的秘密就等于公开了。用 compose 的 environment、编排平台的 Secret 都能解决,选一个顺手的方式坚持用。

实战中的几个高频问题

时区问题迟早撞上。官方基础镜像默认 UTC,容器里日志时间比本地慢八小时,排查问题时对不上时间戳很头疼。轻量解法:运行容器加-e TZ=Asia/Shanghai(compose 里写进 environment 同理),重一点的把宿主机/etc/localtime挂进容器。日志、定时任务、数据库写入都依赖时间,一开始统一好,后面少一堆对时间的活。

MySQL 容器初始化 SQL 怎么自动跑?mysql 官方镜像首次启动时会按字母序执行/docker-entrypoint-initdb.d/目录下的 .sql 和 .sh 文件,compose 里把初始化脚本用 volume 挂进这个目录,第一次启动自动建库建表。注意这个机制只在数据目录为空时执行一次,想重跑就得删卷重来,改脚本再 up。

端口冲突的报错也要会读。docker run提示 “port is already allocated”,说明宿主机端口被占:换个映射端口,或者docker ps -a看看是不是有个旧容器占着,docker rm掉即可。镜像删除报 “image is being used by container”,顺序反了——先删容器再删镜像,反过来就被拦。

最后提醒一句:别把docker exec -it当日常习惯。进容器手改的东西,容器一删全没,这恰恰是无状态设计的本意。要改配置就改 Dockerfile 重新构建,要留数据就挂卷,路径走对了才不反复返工。

还有个实用小习惯:compose 里给数据库这类有状态服务加restart: unless-stopped,宿主机重启后服务自动跟着回来,省去几次半夜被叫醒的麻烦。自建服务多了之后,这个字段几乎是标配。

小结

Docker 入门就三关:概念关(镜像/容器/卷/网络)、命令关(run/ps/logs/exec)、实战关(Dockerfile + Compose)。概念想通、命令练熟、把第一个 Dockerfile 和 compose 文件跑起来,你就正式入门了。往后遇到环境问题,第一反应会变成"起个容器试试",这个习惯一旦养成,开发效率的提升是实实在在的。

有什么问题评论区见,也可以留言说说你想看的下一个主题。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 16:14:12

Codex 接入 DeepSeek API:省钱的替代方案实测

Codex 接入 DeepSeek API&#xff1a;省钱的替代方案实测 Codex 好用&#xff0c;但它的模型调用是有成本的&#xff0c;重度使用一个月下来账单不算便宜。DeepSeek 的 API 价格低一截&#xff0c;而且兼容 OpenAI 的接口协议&#xff0c;这就给"用 Codex 的壳、跑 DeepSe…

作者头像 李华
网站建设 2026/9/3 8:40:35

nssctf_fakebase

下载是个python文件&#xff0c;打开 先解析代码&#xff1a; for i in flag: tmp str(bin(ord(i)))[2:].zfill(8) 每个字符转ascll码的8位二进制后&#xff0c;去除前缀0b,然后左侧补0至8位 b1 int(tmp,2) 把temp的字符串当成二进制解析后再转化为整数 int(tmp, 2) 是 Pytho…

作者头像 李华
网站建设 2026/9/2 9:28:54

记忆与检索

摘要&#xff1a;大语言模型&#xff08;LLM&#xff09;天然具有“无状态&#xff08;Stateless&#xff09;”的特性&#xff0c;每一次交互在底层都是一次独立的数学概率推理。为了让 AI 从简单的“单轮问答机器”演进为能够长期伴随、自主决策的“智能体&#xff08;Agent&…

作者头像 李华
网站建设 2026/9/1 22:38:42

梯度下降法实战:从原理到代码实现Logistics模型参数拟合

1. 项目概述&#xff1a;从“猜”到“算”的拟合思维跃迁 在数学建模和数据分析的实际工作中&#xff0c;我们常常遇到一个核心问题&#xff1a;手里有一堆观测数据&#xff0c;它们背后似乎遵循着某种规律&#xff0c;我们如何找到最能描述这个规律的数学方程&#xff1f;这个…

作者头像 李华
网站建设 2026/9/2 11:49:35

用Codex自动生成Git规范提交信息:从diff到Conventional Commits

刚接触 Git 时&#xff0c;最难写的往往不是命令&#xff0c;而是提交时那一行英文。很多人改完代码&#xff0c;想半天写不出一句像样的 commit message&#xff0c;最后随手敲一个 update 或修改。Codex 的出现让这个问题有了一个很直接的解法&#xff1a;把未提交的 git dif…

作者头像 李华