Ubuntu系统Docker安装部署与容器化应用实战指南

发布时间:2026/8/5 3:49:10
Ubuntu系统Docker安装部署与容器化应用实战指南 1. 为什么在Ubuntu上搞Docker一个运维老兵的视角如果你刚接触Linux服务器或者从Windows/Mac环境转过来听到“Docker”这个词可能会觉得有点玄乎。简单来说它就像一个超级高效的“软件集装箱”系统。以前你要在服务器上跑一个应用比如一个网站后台你得先装好特定版本的Python、一堆依赖库、配置好环境变量过程繁琐还容易出错。换一台服务器这些步骤还得重来一遍版本不一致就可能跑不起来。Docker把应用和它运行所需的一切代码、运行时、系统工具、库、设置打包成一个标准化的“镜像”。这个镜像在任何安装了Docker的机器上都能以完全一致的方式运行起来这个运行起来的实例就叫“容器”。那为什么偏偏是Ubuntu作为全球最流行的Linux桌面和服务器发行版之一Ubuntu的社区支持、软件包生态和稳定性让它成为了学习和部署Docker的绝佳平台。大量的云服务器默认镜像、教程文档都以Ubuntu为例。在Ubuntu上把Docker玩明白了你迁移到CentOS、Debian甚至其他系统基本思路都是相通的只是包管理命令aptvsyum稍有不同。所以今天我们就抛开那些高大上的概念直接上手在Ubuntu上从零开始把Docker装好、用起来并分享一些我这些年踩过坑才总结出来的实操细节。2. 安装前的准备与避坑别急着apt install docker很多人第一步就错了直接sudo apt install docker。在Ubuntu的默认仓库里确实有一个叫docker的包但它通常指的是一个古老的、用于管理系统守护进程的docker一个systemd的替代前端或者是一个版本陈旧的Docker引擎。我们要安装的是Docker Engine官方推荐的安装方式是通过其专属的软件仓库。2.1 清理旧版本与系统更新在开始之前确保你的系统是干净的并且是最新的。这能避免很多因版本冲突导致的灵异问题。首先移除任何可能存在的旧版本Docker如果你是新系统这步可以安全跳过但执行一下也无妨sudo apt remove docker docker-engine docker.io containerd runc注意apt remove不会删除镜像、容器、卷等数据它们通常存放在/var/lib/docker/。如果你需要彻底清空可以手动删除这个目录警告这将永久删除所有Docker数据。接着更新系统包索引并升级现有软件包sudo apt update sudo apt upgrade -y这个-y参数表示自动确认所有提示在脚本或确定要升级时使用很方便。升级后如果内核有更新建议重启系统 (sudo reboot)以确保新内核生效这对Docker的稳定运行很重要。2.2 安装必备的工具集我们需要一些工具来帮助添加Docker的官方软件源。sudo apt install -y ca-certificates curl gnupg lsb-releaseca-certificates: 用于验证HTTPS连接的安全性。curl: 命令行下载工具用来获取GPG密钥和安装脚本。gnupg: GNU隐私卫士用于管理加密密钥这里用来验证Docker软件源的签名。lsb-release: 一个工具可以方便地获取LSBLinux标准基础和发行版信息我们用它来获取系统代号如jammy对应Ubuntu 22.04。2.3 添加Docker官方GPG密钥和软件源这是最关键的一步确保我们下载的软件包来自可信的Docker官方。创建密钥环目录首先为Docker创建一个存放GPG密钥的目录。sudo mkdir -p /etc/apt/keyrings-p参数确保如果目录不存在则创建它。下载并导入GPG密钥从Docker官网下载最新的GPG公钥并存入我们刚创建的目录。curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg拆解一下这个命令curl -fsSL ...:-f失败时不显示HTTP错误-s静默模式-S在静默模式下显示错误-L跟随重定向。组合起来就是“安静地、可靠地下载这个文件”。| sudo gpg --dearmor -o ...: 将curl下载的密钥流通过管道传递给gpg。--dearmor是将ASCII格式的密钥转换为二进制格式.gpg文件。-o指定输出文件路径。设置正确的文件权限确保密钥文件可以被apt正确读取。sudo chmod ar /etc/apt/keyrings/docker.gpg添加Docker软件源现在告诉apt去哪里找Docker的软件包。echo \ deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null这个命令看起来复杂其实是在构造一行软件源配置deb: 表示这是一个二进制软件源。[arch$(dpkg --print-architecture) signed-by...]: 中括号里是选项。arch指定系统架构如amd64,arm64signed-by指定验证签名的密钥文件路径。https://download.docker.com/linux/ubuntu: Docker官方仓库地址。$(lsb_release -cs): 获取当前系统的代号例如jammyUbuntu 22.04、focal20.04。这确保了安装对应你系统版本的Docker。stable: 使用稳定版仓库。还有test测试版和nightly每日构建版生产环境务必用stable。| sudo tee ... /dev/null:echo输出的内容通过管道传给teetee命令将其写入文件/etc/apt/sources.list.d/docker.list同时 /dev/null将屏幕输出丢弃保持终端干净。注意这里有个经典大坑$(lsb_release -cs)在某些非标准或衍生版Ubuntu如Linux Mint上可能返回不兼容的代号导致apt update失败。如果遇到“Release file is not valid yet”或找不到仓库的错误你需要手动将$(lsb_release -cs)替换成对应的Ubuntu基础版本代号。例如Linux Mint 21基于Ubuntu 22.04就应替换为jammy。你可以去查一下你的发行版基于哪个Ubuntu版本。3. 安装与验证让Docker跑起来源加好了安装就简单了。3.1 正式安装Docker引擎再次更新包索引然后安装Docker的核心组件sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugindocker-ce: Docker社区版引擎核心。docker-ce-cli: Docker命令行工具你用docker命令就是它在工作。containerd.io: 行业标准的容器运行时Docker底层用它来管理容器的生命周期。docker-compose-plugin: 这是新版的Docker Compose已集成到Docker CLI中命令为docker compose用于定义和运行多容器应用。旧版独立的docker-compose脚本已不推荐。安装过程会自动启动Docker守护进程dockerd并设置开机自启。你可以用下面命令验证服务状态sudo systemctl status docker看到绿色的active (running)就说明服务在欢快地运行了。按q键退出状态查看。3.2 免sudo运行Docker重要配置默认情况下Docker守护进程以root用户运行而docker命令需要通过Unix socket与守护进程通信该socket文件属于root:docker用户组且权限为rwroot可读写和rwdocker组可读写。新安装后你的普通用户不在docker组里所以执行docker命令需要sudo否则会报“权限被拒绝”。每次都sudo很麻烦也不安全相当于给了普通用户root权限。正确的做法是将你的用户加入docker组sudo usermod -aG docker $USERusermod: 修改用户属性。-aG:-a表示追加append到组-G指定组名。如果不加-a用户会被从其他附加组中移除只保留docker组这可能会影响你使用其他功能比如音频、视频组。$USER: 环境变量代表当前登录的用户名。关键点来了执行完这个命令组信息的变更不会立即生效。它只修改了/etc/group文件当前已登录的会话session持有的组信息缓存并没有更新。你有三种方式让它生效注销并重新登录最彻底的方式。关闭所有终端重新登录系统。启动一个新的登录会话如果你用的是图形界面可以尝试打开一个全新的终端不是从现有终端里新开的标签页。使用newgrp命令临时在当前终端执行newgrp docker。这个命令会启动一个子shell其中组信息被更新。但退出这个子shell后原终端的组信息又会恢复。这通常用于测试。为了确保万无一失我建议直接注销再重新登录。之后打开终端执行groups查看输出中是否包含docker。如果包含了恭喜你现在可以不用sudo直接运行docker命令了。3.3 运行你的第一个容器Hello World验证安装和权限是否正确的经典测试docker run hello-world这个命令会做以下几件事Docker客户端联系本地守护进程。守护进程发现本地没有hello-world:latest这个镜像。守护进程默认从Docker Hub官方公共镜像仓库拉取pull这个镜像。拉取成功后守护进程根据这个镜像创建一个新的容器并运行它。容器内运行一个简单的程序打印出一段欢迎信息和一些说明然后退出。如果你看到类似“Hello from Docker!”的输出并且下面有一大段关于Docker如何工作的说明那么你的Docker安装就大功告成了4. Docker基础操作实战镜像与容器管理安装好了我们来点实在的看看日常怎么跟Docker打交道。4.1 镜像Image操作软件的“安装包”镜像是容器的模板是只读的。你可以从仓库拉取也可以自己构建。搜索镜像在Docker Hub上找找看有没有你需要的软件。docker search nginx会列出所有包含nginx关键词的镜像包括官方镜像OFFICIAL栏标注[OK]和用户贡献的镜像。拉取镜像把镜像下载到本地。docker pull nginx:latestnginx是镜像名latest是标签Tag通常代表最新稳定版。不指定标签时默认就是latest。生产环境强烈建议指定具体版本标签如nginx:1.25-alpine避免因latest更新导致不可预知的变化。列出本地镜像docker images # 或者使用新的镜像管理命令推荐信息更全 docker image ls会显示镜像的仓库名、标签、ID、创建时间和大小。删除镜像当镜像不再需要时。docker rmi hello-world # 或者 docker image rm hello-world如果镜像有对应的容器存在即使容器已停止需要先删除容器才能删除镜像。可以用-f强制删除但不推荐容易留下孤儿容器。4.2 容器Container操作运行中的“软件实例”容器是基于镜像创建的运行实例是可读写的。运行容器最核心的命令。docker run -d --name my-nginx -p 8080:80 nginx:latest这个命令参数有点多拆解一下-d: 后台detached运行。不加这个参数容器会占用当前终端日志直接输出到屏幕上。--name my-nginx: 给容器起个名字方便后续管理。如果不指定Docker会随机分配一个有趣的名字如frosty_gates。-p 8080:80: 端口映射。将宿主机的8080端口映射到容器的80端口。这样你访问宿主机的http://localhost:8080就能看到容器内Nginx的服务了。nginx:latest: 使用的镜像。执行后你会得到一个长长的容器ID。打开浏览器访问http://你的服务器IP:8080应该能看到Nginx的欢迎页面。查看容器docker ps # 查看正在运行的容器 docker ps -a # 查看所有容器包括已停止的查看容器日志容器在后台运行怎么看它的输出docker logs my-nginx可以加-f参数来实时跟踪日志输出就像tail -f一样。进入运行中的容器有时需要排查问题或执行命令。docker exec -it my-nginx /bin/bashexec: 在已运行的容器中执行命令。-it:-i保持标准输入打开-t分配一个伪终端。合起来让你有一个交互式的shell。my-nginx: 容器名或ID。/bin/bash: 要执行的命令这里是启动一个bash shell。有些精简镜像如Alpine Linux可能没有bash要用/bin/sh。停止、启动、重启容器docker stop my-nginx # 优雅停止 docker start my-nginx # 启动已停止的容器 docker restart my-nginx # 重启容器删除容器docker rm my-nginx # 删除已停止的容器 docker rm -f my-nginx # 强制删除运行中的容器不推荐可能丢失数据4.3 数据持久化容器挂了数据不能丢容器本身是临时的停止删除后其内部产生的所有更改如写入的文件、安装的软件都会丢失。为了持久化数据Docker提供了两种主要机制绑定挂载Bind Mounts和卷Volumes。绑定挂载将宿主机上的一个目录或文件直接挂载到容器内。简单直接但依赖于宿主机的目录结构。docker run -d --name nginx-with-html -v /home/user/my-html:/usr/share/nginx/html:ro -p 8081:80 nginx-v /宿主机路径:/容器路径:ro。ro表示只读read-only容器不能修改这个目录。不加则默认可读写。这样你只需修改/home/user/my-html下的文件就能实时影响Nginx服务的内容。卷Volumes由Docker管理的数据存储区域完全独立于宿主机目录结构和容器的生命周期。是Docker推荐的数据持久化方式。# 创建一个卷 docker volume create my-nginx-data # 使用卷运行容器 docker run -d --name nginx-with-vol -v my-nginx-data:/usr/share/nginx/html -p 8082:80 nginx # 查看卷详情 docker volume inspect my-nginx-data卷的数据存储在宿主机上但路径由Docker管理通常在/var/lib/docker/volumes/下。删除容器不会删除卷需要手动docker volume rm。卷更容易备份、迁移且性能通常优于绑定挂载。5. 使用Docker Compose编排多容器应用单个容器还好管理但一个稍微复杂的应用比如一个Web应用需要数据库、缓存、后端服务、前端服务就需要多个容器协同工作。手动用docker run一个个启动、配置网络、链接非常繁琐且容易出错。Docker Compose就是来解决这个问题的。前面我们安装了docker-compose-plugin它提供了docker compose命令注意是空格不是横杠。我们通过一个经典的WordPress博客系统例子来感受一下。5.1 编写docker-compose.yml文件在一个空目录下比如~/my-wordpress创建一个名为docker-compose.yml的文件version: 3.8 # 指定Compose文件格式版本 services: db: image: mysql:8.0 # 环境变量用于配置MySQL environment: MYSQL_ROOT_PASSWORD: somewordpress MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD: wordpress # 使用卷持久化数据库数据 volumes: - db_data:/var/lib/mysql # 健康检查确保数据库就绪后WordPress再启动 healthcheck: test: [CMD, mysqladmin, ping, -h, localhost] timeout: 20s retries: 10 wordpress: depends_on: db: condition: service_healthy # 等待db服务健康 image: wordpress:latest ports: - 8000:80 # 将宿主机的8000端口映射到WordPress容器的80端口 environment: WORDPRESS_DB_HOST: db:3306 # 注意这里使用服务名db作为主机名 WORDPRESS_DB_USER: wordpress WORDPRESS_DB_PASSWORD: wordpress WORDPRESS_DB_NAME: wordpress volumes: - wp_data:/var/www/html # 持久化WordPress网站文件主题、插件、上传内容 # 定义在文件底部声明我们要使用的卷 volumes: db_data: wp_data:这个文件定义了两个服务db和wordpress以及两个匿名卷db_data和wp_data。关键点depends_on: 定义服务启动依赖顺序。这里wordpress依赖db。condition: service_healthy: 这是Compose V2.1的语法表示不仅等db启动还要等它通过健康检查。这比单纯等容器running状态更可靠。WORDPRESS_DB_HOST: db:3306: 这是Docker Compose的魔法之一。在同一个Compose项目中服务之间可以使用服务名作为主机名进行网络通信。这里wordpress容器可以通过db这个主机名连接到MySQL容器无需知道其IP地址。Docker会内置创建一个网络并配置好DNS。5.2 启动、管理与停止整个应用在包含docker-compose.yml文件的目录下执行启动所有服务后台运行docker compose up -d-d同样是后台运行。Docker Compose会拉取镜像如果本地没有、创建网络、创建卷然后按依赖顺序启动所有服务。查看服务状态docker compose ps这会列出当前目录下Compose项目中的所有容器类似docker ps但只限于本项目。查看服务日志docker compose logs # 查看所有服务日志 docker compose logs wordpress # 只看wordpress服务的日志 docker compose logs -f wordpress # 实时跟踪wordpress日志停止服务docker compose down这会停止并删除所有容器、网络默认创建的网络但不会删除卷。卷里保存着你的数据库和网站文件所以下次docker compose up -d时数据还在。停止并删除所有资源包括卷docker compose down -v警告-v会删除docker-compose.yml中声明的所有匿名卷和命名卷数据将永久丢失生产环境慎用。现在打开浏览器访问http://你的服务器IP:8000你应该能看到WordPress的安装界面了。整个过程中你不需要在宿主机上单独安装和配置MySQL或PHP所有依赖都被封装在各自的容器里通过一个YAML文件统一管理。这就是Docker Compose的魅力。6. 生产环境注意事项与性能调优在个人开发机上玩玩和在生产服务器上稳定运行是两回事。下面是一些我踩过坑后总结的要点。6.1 镜像选择与安全不要盲目使用latest标签latest是流动的今天和明天的版本可能不同。生产环境必须使用固定版本标签例如nginx:1.25-alpine、python:3.11-slim。这能保证部署的一致性。优先选择官方镜像Official ImageDocker Hub上带有[OK]标志的官方镜像由软件供应商或Docker官方维护通常更安全、更新更及时、文档更全。使用最小化基础镜像Alpine Linux镜像通常只有几MB比完整的Ubuntu、Debian镜像上百MB小得多攻击面也小。例如nginx:alpine、python:3.11-alpine。但要注意Alpine使用musl libc而不是glibc某些依赖glibc的二进制软件可能无法运行需要测试。定期更新镜像即使使用固定版本也需要定期检查并更新到该版本的最新补丁版以修复安全漏洞。可以结合CI/CD流水线自动扫描镜像漏洞如使用trivy、grype等工具。6.2 资源限制与监控默认情况下容器可以使用宿主机的所有可用资源CPU、内存。一个失控的容器可能拖垮整个主机。限制容器资源在docker run或docker-compose.yml中设置资源限制。docker run -d --name limited-nginx \ --memory512m --memory-swap1g \ # 内存限制512MB交换分区1G --cpus1.5 \ # 最多使用1.5个CPU核心 nginx在docker-compose.yml中services: myapp: image: myapp:latest deploy: # 注意在Compose中资源限制通常在deploy下兼容Swarm模式单机也可用 resources: limits: cpus: 0.5 memory: 512M reservations: cpus: 0.25 memory: 256Mlimits是硬限制reservations是预留资源。监控Docker使用docker stats命令可以实时查看所有容器的CPU、内存、网络IO、块IO使用情况。对于生产环境需要集成到更专业的监控系统如Prometheus Grafana配合cAdvisor容器监控工具。6.3 日志与数据管理配置日志驱动和轮转默认的json-file日志驱动会不断写日志文件可能占满磁盘。可以配置日志轮转。# 全局配置修改 /etc/docker/daemon.json { log-driver: json-file, log-opts: { max-size: 10m, # 单个日志文件最大10MB max-file: 3 # 最多保留3个日志文件10m, 10m, 10m } }修改后需要重启Docker服务sudo systemctl restart docker。卷备份定期备份重要的Docker卷数据。卷的物理路径可以通过docker volume inspect 卷名查看。备份时可以运行一个临时容器挂载需要备份的卷和宿主机备份目录然后用tar或rsync进行备份。docker run --rm -v my-nginx-data:/source -v /host/backup:/backup alpine tar czf /backup/nginx-data-$(date %Y%m%d).tar.gz -C /source .6.4 网络与安全用户命名空间映射User Namespace Remapping默认情况下容器内的root用户映射到宿主机的root用户。如果容器被攻破攻击者可能获得宿主机的高权限。可以启用用户命名空间映射让容器内的root映射到宿主机的一个非特权用户。这需要在Docker守护进程配置中启用相对复杂但对于多租户或安全要求高的环境值得考虑。避免在容器内运行SSH服务这是一个常见的反模式。管理容器应该通过docker exec或通过宿主机的SSH连接到宿主机再操作Docker。在容器内运行SSH会增加攻击面和维护负担。理解Docker网络模式bridge默认容器通过虚拟网桥连接、host容器直接使用宿主机网络栈性能好但隔离性差、none无网络。根据应用需求选择。Compose默认会为项目创建一个独立的bridge网络。7. 进阶之路自定义镜像与Dockerfile从公共镜像仓库拉取镜像很方便但总有一天你需要构建自己的镜像比如将你的应用代码打包进去。这就需要用到Dockerfile——一个文本文件包含了一系列构建镜像的指令。7.1 编写一个简单的Dockerfile假设我们有一个简单的Python Flask应用。项目结构如下my-flask-app/ ├── app.py ├── requirements.txt └── Dockerfileapp.py:from flask import Flask app Flask(__name__) app.route(/) def hello(): return Hello, Docker! if __name__ __main__: app.run(host0.0.0.0, port5000)requirements.txt:Flask2.3.3Dockerfile:# 第一阶段构建阶段可选用于多阶段构建减小最终镜像 # 使用官方Python轻量级镜像作为构建环境 FROM python:3.11-slim AS builder WORKDIR /app # 复制依赖文件 COPY requirements.txt . # 安装依赖到特定目录便于复制到运行阶段 RUN pip install --user --no-cache-dir -r requirements.txt # 第二阶段运行阶段 FROM python:3.11-slim # 设置工作目录 WORKDIR /app # 从构建阶段复制已安装的Python包 COPY --frombuilder /root/.local /root/.local # 确保脚本能找到pip安装的命令 ENV PATH/root/.local/bin:$PATH # 复制应用代码 COPY app.py . # 声明容器运行时监听的端口 EXPOSE 5000 # 定义容器启动时执行的命令 CMD [python, app.py]这个Dockerfile做了几件事多阶段构建第一阶段builder只负责安装依赖。第二阶段基于一个干净的slim镜像只从第一阶段复制安装好的依赖/root/.local不包含构建工具和中间文件使得最终镜像非常小巧。WORKDIR: 设置工作目录相当于cd到这个目录后续的COPY、RUN、CMD等命令都在此目录下执行。COPY: 将宿主机文件复制到镜像内。RUN: 在构建镜像时执行命令这里用来安装依赖。ENV: 设置环境变量这里把用户安装的Python包路径加入PATH。EXPOSE: 声明容器打算使用的端口这是一个文档性质的指令实际发布端口需要在docker run时用-p映射。CMD: 指定容器启动时默认执行的命令。一个Dockerfile只能有一个CMD指令。7.2 构建并运行自定义镜像在my-flask-app目录下执行构建命令docker build -t my-flask-app:1.0 .-t my-flask-app:1.0: 给镜像打上标签名称:版本。.: 构建上下文路径Docker客户端会将这个目录下的所有文件发送给Docker守护进程。所以通常会在一个干净的目录下放Dockerfile避免复制不必要的文件如node_modules,.git可以通过.dockerignore文件来排除。构建完成后运行它docker run -d -p 5000:5000 --name my-flask-container my-flask-app:1.0访问http://你的服务器IP:5000就能看到“Hello, Docker!”了。7.3 Dockerfile最佳实践与踩坑点使用.dockerignore文件像.gitignore一样列出不需要发送到Docker守护进程的文件和目录能显著加速构建过程减少镜像大小。例如**/__pycache__ **/*.pyc **/.git **/node_modules **/.env Dockerfile README.md合并RUN指令每一条RUN指令都会在镜像中创建一个新的层。为了减少层数从而减小镜像大小尽量将相关的命令合并到一条RUN指令中并用连接用\换行保持可读性。# 不推荐 RUN apt update RUN apt install -y package1 package2 RUN rm -rf /var/lib/apt/lists/* # 推荐 RUN apt update \ apt install -y package1 package2 \ rm -rf /var/lib/apt/lists/*注意指令顺序Docker会缓存每一层。如果某一层及其之前的所有层没有变化Docker会直接使用缓存。因此应该将变化最频繁的层如复制应用代码COPY . .放在Dockerfile的后面将变化最少的层如安装系统依赖放在前面以充分利用缓存加速构建。以非root用户运行容器默认情况下容器内的进程以root运行。为了安全最好创建一个非root用户并切换。RUN groupadd -r appuser useradd -r -g appuser appuser USER appuser # 后续的CMD等指令将以appuser身份执行注意如果应用需要绑定1024以下的端口如80、443非root用户没有权限。这时可以在docker run时使用-p 8080:80映射到高位端口或者使用setcap赋予二进制文件特殊权限更复杂。理解CMD与ENTRYPOINTCMD: 提供容器默认的执行命令和参数。可以被docker run后面的命令覆盖。ENTRYPOINT: 配置容器启动时执行的可执行文件。CMD的内容会作为参数传递给ENTRYPOINT。 通常组合使用ENTRYPOINT [executable]CMD [arg1, arg2]。这样docker run image会执行executable arg1 arg2而docker run image arg3则会执行executable arg3CMD被覆盖。从在Ubuntu上安装Docker到运行第一个容器再到使用Compose编排复杂应用最后构建自己的镜像这条路径基本覆盖了Docker的日常核心使用场景。技术工具的学习关键在于动手去试、去踩坑。遇到问题善用docker logs、docker exec进去看看查阅官方文档大部分都能解决。把它用在你自己的项目里无论是部署一个简单的博客还是搭建一套复杂的微服务开发环境Docker都能让整个过程变得清晰、可控且高效。