Docker入门指南:从Hello World到容器化部署实战

📅 2026/8/5 9:47:48
Docker入门指南:从Hello World到容器化部署实战
1. 从“Hello, World!”到容器化思维为什么Docker是你的第一站如果你刚开始接触软件开发、运维或者只是单纯地对“云原生”、“微服务”这些听起来很酷的词感到好奇那么Docker几乎是你绕不开的起点。很多人把Docker的第一个“Hello, World!”程序看作一个简单的入门仪式就像学编程时在屏幕上打印出第一行字。但在我看来这个简单的操作背后隐藏着理解现代软件开发和部署范式的钥匙。它不仅仅是一个命令更是一次思维模式的切换——从“在我的机器上能跑”到“在任何地方都能以相同的方式跑”。为什么是Docker想象一下你开发了一个基于Python Flask的Web应用本地测试一切正常。当你兴冲冲地把代码和一份requirements.txt文件交给运维同事或者部署到云服务器上时却可能遭遇一连串的“玄学”问题服务器上的Python版本是3.6而你的代码需要3.9某个底层C库的版本不匹配甚至操作系统比如CentOS和Ubuntu的细微差异都可能导致应用行为异常。这就是经典的“开发环境-测试环境-生产环境”不一致的困境。Docker通过容器技术将应用及其所有依赖代码、运行时、系统工具、系统库、设置打包成一个标准化的、轻量级的、可移植的“镜像”。这个镜像在任何安装了Docker引擎的机器上都能以完全一致的方式运行起来彻底解决了“环境依赖”这个老大难问题。所以运行第一个Docker的“Hello, World!”其意义远大于屏幕上输出一行文字。它是在验证你的Docker环境是否就绪是在体验“一次构建处处运行”的魔力更是为你后续构建、分享和部署更复杂的应用打下坚实的基础。无论你是前端、后端、数据科学家还是运维工程师掌握Docker都意味着你拥有了让应用交付变得更可靠、更高效的核心能力。接下来我们就从零开始完成这个仪式并深入理解那些让你真正驾驭Docker的常用命令。2. 环境准备不仅仅是安装一个软件在兴奋地敲下第一个docker run命令之前我们必须确保战场——也就是你的计算机环境——已经准备妥当。Docker的安装过程因操作系统而异并且会涉及到一些底层系统特性的检查这也是很多新手遇到的第一个“拦路虎”。我们分别看看在主流操作系统上的准备要点。2.1 Windows与macOS拥抱Docker Desktop对于Windows和macOS用户官方推荐且最便捷的方式是安装Docker Desktop。它是一个集成了Docker引擎、Docker CLI客户端、Docker Compose、Kubernetes等工具的一体化桌面应用。Windows用户特别注意这是最容易出问题的环节。Docker依赖于操作系统的虚拟化技术Hyper-V或WSL 2后端。安装时Docker Desktop会尝试自动启用这些功能但有时会失败并弹出经典的错误提示“Docker Desktop failed to start because virtualisation support wasn’t detected”。这个错误的根源通常有两个BIOS/UEFI中的虚拟化未开启这是硬件层面的支持。你需要重启电脑进入BIOS/UEFI设置通常在开机时按F2、Del、F10等键找到类似“Intel Virtualization Technology (VT-x)”或“AMD-V”的选项将其设置为“Enabled”。Windows功能中的“Hyper-V”或“Windows Subsystem for Linux”未启用这是操作系统层面的支持。即使BIOS开启了虚拟化Windows也可能默认关闭相关功能。你需要打开“控制面板 - 程序和功能 - 启用或关闭Windows功能”确保“Hyper-V”和“适用于Linux的Windows子系统”被勾选如果使用WSL 2后端。启用后需要重启。提示对于Windows 10家庭版它不支持Hyper-V。此时Docker Desktop会默认使用WSL 2后端。你必须先安装WSL 2包括一个Linux发行版如Ubuntu并在Docker Desktop设置中指定使用WSL 2。macOS用户过程相对简单。确保你的macOS版本符合要求通常是较新的版本直接从Docker官网下载.dmg文件安装即可。对于基于Apple SiliconM1/M2等的MacDocker Desktop提供了原生ARM版本性能很好。安装成功后启动Docker Desktop你会在系统托盘Windows或菜单栏macOS看到Docker的鲸鱼图标。等待其状态变为“Docker Desktop is running”就表示环境就绪了。2.2 Linux原生的高效体验Linux是Docker的“老家”安装方式多样且直接。不同的发行版有不同的包管理工具。Ubuntu/Debian通常使用APT仓库安装。# 1. 更新软件包索引并安装依赖 sudo apt-get update sudo apt-get install ca-certificates curl gnupg # 2. 添加Docker官方GPG密钥和仓库 sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod ar /etc/apt/keyrings/docker.gpg echo \ deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release echo $VERSION_CODENAME) stable | \ sudo tee /etc/apt/sources.list.d/docker.list /dev/null # 3. 安装Docker引擎 sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin # 4. 验证安装 sudo docker run hello-worldCentOS/RHEL使用YUM仓库。# 1. 安装yum工具集 sudo yum install -y yum-utils # 2. 添加Docker仓库 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 3. 安装Docker引擎 sudo yum install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin # 4. 启动Docker服务并设置开机自启 sudo systemctl start docker sudo systemctl enable docker # 5. 验证安装 sudo docker run hello-world一个关键步骤将用户加入docker组Linux默认情况下在Linux上运行Docker命令需要sudo权限。为了避免每次输入sudo你可以将当前用户加入docker用户组sudo usermod -aG docker $USER执行此命令后必须完全注销并重新登录或者重启终端用户组变更才会生效。之后你就可以直接运行docker命令了。2.3 安装后的重要配置镜像加速由于Docker Hub默认的公共镜像仓库位于海外国内直接拉取镜像速度可能很慢甚至失败。配置一个国内的镜像加速器是提升体验的关键一步。Docker Desktop (Windows/macOS)在设置Settings中找到“Docker Engine”编辑其中的JSON配置添加镜像加速器地址。例如使用阿里云加速器需要先登录阿里云容器镜像服务获取专属地址{ registry-mirrors: [https://your-mirror.mirror.aliyuncs.com] }Linux编辑/etc/docker/daemon.json文件如果不存在则创建{ registry-mirrors: [https://your-mirror.mirror.aliyuncs.com] }然后重启Docker服务sudo systemctl restart docker完成以上所有步骤后你的Docker战场才算真正准备完毕。打开终端或命令提示符/PowerShell输入docker version如果能看到Client和Server的版本信息恭喜你可以开始真正的旅程了。3. 神圣的“Hello, World!”深入理解docker run现在让我们执行那个具有里程碑意义的命令docker run hello-world如果你的网络和配置都正确终端会滚动输出一段欢迎文字。这个过程看似简单背后却是一系列精密的操作。让我们拆解docker run hello-world到底做了什么解析镜像名Docker CLI接收到hello-world这个镜像名。由于没有指定仓库地址它默认去Docker Hubdocker.io的官方仓库查找。检查本地镜像Docker引擎首先检查本地是否存在名为hello-world:latest的镜像未指定标签时默认为latest。拉取镜像因为本地没有引擎会从配置的镜像仓库默认是Docker Hub拉取hello-world:latest镜像。你会看到Unable to find image hello-world:latest locally和latest: Pulling from library/hello-world的输出。创建容器镜像拉取到本地后Docker引擎会基于这个镜像创建一个新的、可写的容器层。容器是镜像的运行实例。运行默认命令hello-world镜像的Dockerfile中指定了一个默认命令通常是/hello。Docker引擎在容器内部执行这个命令。输出并退出该命令执行的结果就是打印出那段欢迎信息到你的终端。命令执行完毕后容器进程终止容器进入“Exited”状态。关键概念辨析镜像 vs. 容器这是Docker中最核心的一对概念必须理解透彻。镜像Image一个只读的模板。它包含了运行应用所需的文件系统、依赖和配置。你可以把它理解为一个软件的“安装包”或“蓝图”。hello-world就是一个非常小的镜像。容器Container镜像的一个运行实例。当你执行docker run时就是从镜像创建了一个容器。容器是动态的、有生命周期的创建、运行、停止、删除。容器在镜像的只读层之上添加了一个可写的“容器层”所有运行时的修改都发生在这个层不会影响底层的镜像。一个镜像可以创建出多个相互独立的容器。所以docker run实际上是一个复合命令它包含了docker pull如果本地没有镜像、docker create创建容器和docker start启动容器等多个步骤。理解了这一点我们再来看一个更接近真实场景的例子docker run -d -p 8080:80 --name my-nginx nginx-d让容器在后台运行detached mode。-p 8080:80进行端口映射。将宿主机的8080端口映射到容器的80端口。这样你访问http://localhost:8080就能看到容器内Nginx的服务。--name my-nginx给容器起一个名字方便后续管理。如果不指定Docker会随机生成一个名字。nginx要运行的镜像名这里拉取并运行官方的Nginx镜像。执行后你可以用docker ps看到这个正在运行的容器用浏览器访问localhost:8080就能看到Nginx的欢迎页。这比hello-world更直观地展示了Docker如何运行一个真实的服务。4. 镜像管理获取、查看与清理镜像是容器世界的基石。管理好镜像是高效使用Docker的前提。4.1 拉取镜像docker pull虽然docker run会自动拉取不存在的镜像但显式使用docker pull可以让你更灵活地控制拉取过程例如只拉取不运行或者拉取特定标签的镜像。# 拉取最新标签的Ubuntu镜像 docker pull ubuntu # 拉取指定标签的镜像例如Ubuntu 20.04 docker pull ubuntu:20.04 # 从特定仓库拉取镜像非Docker Hub docker pull registry.example.com/myapp:v1.0关于镜像标签Tag标签通常用于标识镜像的不同版本。latest是一个特殊的、移动的标签通常指向该仓库的最新稳定版。在生产环境中强烈建议使用明确的版本标签如nginx:1.25-alpine而不是latest以确保环境的一致性。4.2 查看本地镜像docker images或docker image ls这个命令列出所有本地存储的镜像。docker images # 或者 docker image ls # 输出示例 # REPOSITORY TAG IMAGE ID CREATED SIZE # nginx latest 2b7d6430f78d 2 weeks ago 142MB # ubuntu 20.04 ba6acccedd29 2 months ago 72.8MB # hello-world latest d2c94e258dcb 9 months ago 13.3kB你可以看到镜像的仓库名、标签、唯一的镜像ID、创建时间和大小。镜像ID是镜像的唯一标识在很多命令中可以只用其前几位能唯一区分即可来指代镜像。4.3 删除镜像docker rmi或docker image rm当镜像不再需要时可以删除以释放磁盘空间。# 通过镜像名删除 docker rmi hello-world # 通过镜像ID删除取前几位即可 docker rmi d2c94e # 强制删除即使有容器基于此镜像慎用 docker rmi -f ubuntu:20.04 # 删除所有未被使用的镜像悬空镜像dangling images docker image prune重要提示如果镜像有正在运行的容器或者有其它镜像依赖它作为父镜像则无法直接删除。你需要先停止并删除相关容器。docker image prune是一个清理利器可以安全地删除那些没有标签且没有被任何容器引用的中间镜像它们通常是在构建新镜像过程中产生的。4.4 搜索镜像docker search如果你想在Docker Hub上寻找某个软件的镜像可以使用此命令。docker search nginx不过更常见的做法是直接去Docker Hub网站hub.docker.com进行搜索因为网站提供了更详细的描述、标签列表、使用统计和Dockerfile链接。5. 容器生命周期管理从创建到销毁容器是有状态的、有生命周期的对象。掌握其生命周期的管理命令是日常工作的核心。5.1 查看容器docker ps这是使用频率最高的命令之一用于列出容器。# 查看正在运行的容器 docker ps # 查看所有容器包括已停止的 docker ps -a # 查看最近创建的容器 docker ps -l # 以简洁格式只显示容器ID docker ps -q-a参数至关重要因为很多问题排查需要查看已经停止的容器。5.2 停止与启动容器docker stopdocker start# 停止一个运行中的容器发送SIGTERM信号允许优雅退出 docker stop my-nginx # 强制停止一个容器发送SIGKILL信号立即终止 docker kill my-nginx # 启动一个已停止的容器 docker start my-nginx # 重启容器先stop再start docker restart my-nginxstop和kill的区别在于信号的温和程度。stop是首选它给容器内进程时间进行清理工作kill则用于容器无响应时。5.3 进入容器内部docker exec这是调试和排查问题的神器。它可以在一个正在运行的容器内部启动一个新的进程通常是启动一个shell。# 以交互模式进入容器并启动一个bash shell docker exec -it my-nginx /bin/bash # 在容器内执行一个一次性命令例如查看进程 docker exec my-nginx ps aux-i保持标准输入STDIN打开允许你与命令交互。-t分配一个伪终端pseudo-TTY让shell看起来像是在本地终端一样。-it通常组合使用用于交互式会话。注意docker exec和早期另一个命令docker attach不同。attach是连接到容器的主进程PID 1如果主进程是一个Web服务器你attach进去看到的是它的日志输出而不是一个shell。而exec是新建一个进程更为常用和安全。5.4 查看容器日志docker logs容器内应用输出的标准输出STDOUT和标准错误STDERR会被Docker引擎捕获成为容器的日志。# 查看容器日志 docker logs my-nginx # 实时跟踪日志输出类似 tail -f docker logs -f my-nginx # 查看最近10行日志 docker logs --tail 10 my-nginx # 查看某个时间点之后的日志 docker logs --since 2024-01-01T00:00:00 my-nginx当你的容器内应用没有按预期工作时第一时间查看日志是定位问题的标准操作流程。5.5 删除容器docker rm容器停止后其文件系统可写层仍然占用磁盘空间需要手动删除。# 删除一个已停止的容器 docker rm my-nginx # 强制删除一个容器即使正在运行 docker rm -f my-nginx # 删除所有已停止的容器清理空间常用 docker container prune和镜像一样prune命令是清理的好帮手。docker container prune会交互式地询问你是否删除所有已停止的容器。5.6 容器与宿主机的文件交换docker cp有时你需要将宿主机的文件复制到容器内或者将容器内的文件复制到宿主机进行分析。# 将宿主机文件复制到容器内 docker cp /path/on/host/file.txt my-nginx:/path/in/container/ # 将容器内文件复制到宿主机 docker cp my-nginx:/var/log/nginx/error.log /path/on/host/这是一个非常实用的调试和备份工具。6. 构建自定义镜像理解Dockerfile运行现成的镜像只是开始真正的威力在于构建属于自己的镜像。这通过一个名为Dockerfile的文本文件来实现。Dockerfile包含了一系列指令描述了如何一步步组装出一个镜像。让我们创建一个最简单的自定义镜像。新建一个目录在里面创建两个文件1. 编写应用文件app.py#!/usr/bin/env python3 from flask import Flask app Flask(__name__) app.route(/) def hello(): return Hello, Dockerized World!\n if __name__ __main__: app.run(host0.0.0.0, port5000)这是一个简单的Python Flask应用。2. 编写依赖文件requirements.txtFlask2.3.33. 编写Dockerfile# 第一阶段使用官方Python轻量级镜像作为构建环境 FROM python:3.9-slim AS builder # 设置工作目录 WORKDIR /app # 将依赖文件复制到工作目录 COPY requirements.txt . # 安装依赖到 /usr/local RUN pip install --no-cache-dir --user -r requirements.txt # 第二阶段创建更小的运行时镜像 FROM python:3.9-slim # 设置工作目录 WORKDIR /app # 从构建阶段复制已安装的Python包 COPY --frombuilder /root/.local /root/.local # 将应用代码复制到工作目录 COPY app.py . # 确保运行时可以找到用户安装的包 ENV PATH/root/.local/bin:$PATH # 声明容器运行时监听的端口 EXPOSE 5000 # 定义容器启动时执行的命令 CMD [python, app.py]这个Dockerfile做了几件关键事情FROM指定基础镜像。我们选择官方的python:3.9-slim它是一个删减了非必要文件的Python环境比完整版小很多。WORKDIR设置容器内的工作目录后续的COPY、RUN等命令都会在此目录下执行。COPY将宿主机的文件或目录复制到镜像中。RUN在构建镜像时执行命令。这里用于安装Python依赖。ENV设置环境变量。这里将用户安装包的路径添加到PATH中。EXPOSE声明容器运行时监听的端口。这只是一个文档说明实际映射需要在docker run时用-p参数指定。CMD指定容器启动时默认运行的命令。一个Dockerfile中只能有一个CMD指令。4. 构建镜像 在包含Dockerfile、app.py和requirements.txt的目录下执行docker build -t my-flask-app:1.0 .-t为镜像打标签格式为name:tag。.指定构建上下文context的路径。Docker客户端会将这个目录下的所有文件打包发送给Docker守护进程因此要避免包含不必要的文件如node_modules,.git通常使用.dockerignore文件来排除。5. 运行自定义镜像docker run -d -p 5000:5000 --name my-app my-flask-app:1.0访问http://localhost:5000你应该能看到“Hello, Dockerized World!”。通过Dockerfile构建镜像你将应用代码、运行环境、配置全部固化到了一个可移植的镜像中。这是实现持续集成和持续部署CI/CD的基石。7. 数据持久化与网络超越单容器当应用变得复杂你会面临两个新问题容器内的数据如何持久化多个容器之间如何通信7.1 数据卷Volumes持久化数据的正确方式容器本身是易失的ephemeral。当容器被删除其可写层中的所有数据也会丢失。为了持久化数据如数据库文件、配置文件、日志Docker提供了数据卷机制。数据卷是宿主机文件系统中由Docker管理的一个特殊目录它独立于容器的生命周期。即使容器被删除数据卷依然存在。# 创建一个命名数据卷 docker volume create my-data # 运行容器并挂载数据卷到容器内的路径 docker run -d -p 8080:80 \ --name my-nginx-with-data \ -v my-data:/usr/share/nginx/html \ nginx # 查看所有数据卷 docker volume ls # 查看数据卷详情包括在宿主机上的实际路径 docker volume inspect my-data # 删除未使用的数据卷 docker volume prune在上面的例子中我们将名为my-data的数据卷挂载到了Nginx容器存放网页文件的目录。你对/usr/share/nginx/html目录的任何修改实际上都保存在了宿主机的my-data卷中。下次你用同样的卷启动一个新容器数据依然存在。绑定挂载Bind Mounts另一种方式是将宿主机的特定目录直接挂载到容器内。这在开发时非常方便因为你可以即时在宿主机修改代码容器内立即生效。docker run -d -p 5000:5000 \ --name my-dev-app \ -v /path/to/your/code:/app \ my-flask-app:1.0这里宿主机的/path/to/your/code目录被挂载到容器的/app目录覆盖了镜像中原来的内容。7.2 容器网络让容器彼此发现默认情况下每个容器都运行在独立的网络命名空间中拥有自己的网络栈。Docker提供了几种网络模式最常用的是桥接网络bridge。当你安装Docker时它会自动创建一个名为bridge的默认网络。通过docker run启动的容器如果没有指定网络就会连接到这个默认的桥接网络。在这个网络内的容器可以通过IP地址相互通信但无法通过容器名通信。为了更好的服务发现我们可以创建自定义的桥接网络。# 创建一个自定义桥接网络 docker network create my-app-network # 运行一个MySQL容器并连接到自定义网络 docker run -d \ --name mysql-db \ --network my-app-network \ -e MYSQL_ROOT_PASSWORDsecret \ mysql:8.0 # 运行一个Web应用容器连接到同一个网络 docker run -d \ --name web-app \ --network my-app-network \ -p 8080:80 \ -e DATABASE_HOSTmysql-db \ my-web-app:latest关键点在于--network my-app-network。在同一个自定义桥接网络中的容器不仅可以通过IP通信还可以直接使用容器名如mysql-db作为主机名进行通信。这对于多容器应用例如一个Web应用容器和一个数据库容器的编排至关重要。在上面的例子中web-app容器可以通过mysql-db这个主机名连接到MySQL服务而无需知道其具体IP地址。8. 多容器应用编排初探Docker Compose当你需要管理多个相互关联的容器比如一个Web前端、一个后端API、一个数据库、一个缓存时手动使用docker run并设置网络、卷、环境变量会变得非常繁琐且容易出错。Docker Compose就是为解决这个问题而生的工具。它允许你使用一个YAML文件docker-compose.yml来定义和运行多容器应用。假设我们有一个简单的“WordPress MySQL”博客应用。创建一个docker-compose.yml文件version: 3.8 services: db: image: mysql:8.0 # 数据持久化 volumes: - db_data:/var/lib/mysql # 重启策略除非手动停止否则总是重启 restart: always environment: MYSQL_ROOT_PASSWORD: some_root_password MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD: wordpress_password # 自定义网络服务间可通过服务名通信 networks: - wp-network wordpress: depends_on: - db image: wordpress:latest # 端口映射 ports: - 8080:80 restart: always environment: WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_USER: wordpress WORDPRESS_DB_PASSWORD: wordpress_password WORDPRESS_DB_NAME: wordpress volumes: - wp_data:/var/www/html networks: - wp-network # 定义数据卷由Docker Compose管理 volumes: db_data: wp_data: # 定义网络由Docker Compose管理 networks: wp-network: driver: bridge在这个文件中我们定义了两个服务db和wordpress一个自定义网络wp-network和两个数据卷db_data,wp_data。depends_on确保了wordpress服务会在db服务启动之后启动。现在只需要在包含docker-compose.yml的目录下执行一条命令# 启动所有服务在后台运行 docker-compose up -d # 查看运行状态 docker-compose ps # 查看服务日志可以指定服务名 docker-compose logs -f wordpress # 停止并移除所有容器、网络但保留数据卷 docker-compose down # 停止并移除所有容器、网络同时删除数据卷数据会丢失 docker-compose down -vDocker Compose极大地简化了多容器应用的开发、测试和部署流程。它让你可以用声明式的方式定义整个应用栈并通过简单的命令控制其生命周期。对于本地开发环境搭建和简单的生产部署Docker Compose是一个非常强大且实用的工具。从运行第一个hello-world到管理镜像、容器再到理解数据持久化、网络最后用Docker Compose编排复杂应用这条路径清晰地勾勒出了一名开发者从Docker新手到熟练用户的成长轨迹。每个命令背后都对应着容器技术的一个核心概念理解它们你就能真正将Docker化为己用构建出稳定、可移植的现代化应用。