$ cat post.md

Docker 入门实战:在 Ubuntu 上跑起你的第一个容器

为什么折腾服务器的人都用 Docker:官方源安装、镜像/容器/卷/端口四个核心概念、跑一个带数据的容器,最后用 compose 编排收尾。

Ubuntu 系列完结后,下一个绕不开的话题就是 Docker。你在任何一个「自托管 XX 服务」的教程里都会看到它的身影。这篇不讲虚的,目标是让你在半小时内理解 Docker 的四个核心概念,并在自己的服务器上跑起真正有数据的容器。

为什么用 Docker,而不是 apt 直接装

apt 装软件本来挺顺,但自托管服务一多就会撞上这些墙:

  • 依赖打架:服务 A 要 Python 3.10,服务 B 要 3.12,同一台机器上互相折磨。
  • 卸载不干净:配置文件、数据目录散落各处,想删干净得满盘找。
  • 环境不可复制:这台机器调好的服务,换台机器全部重来。

Docker 的思路是把每个服务和它的全部依赖打成一个集装箱:互不干扰、一条命令起停、删掉容器就删得干干净净、换个机器一条命令原地复活。这就是为什么自托管教程清一色用 Docker。

第一步:安装 Docker

Ubuntu 仓库里有 Docker 但版本旧,推荐用官方源(不要用 snap 版,坑多):

# 装依赖
sudo apt update
sudo apt install -y ca-certificates curl

# 添加 Docker 官方 GPG key
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# 添加软件源
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 安装
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

两件收尾的事:

# 验证安装
sudo docker run hello-world

# 把当前用户加进 docker 组,以后敲命令不用 sudo(重新登录生效)
sudo usermod -aG docker $USER

注意官方源在国内可能很慢,可以先给 apt 走代理,或者搜「Docker 镜像加速器」配 registry-mirrors。

第二步:四个核心概念,一个例子讲透

Docker 只需要理解四个词:

概念 类比 说明
镜像(Image) 安装光盘 只读模板,比如 nginx:latest
容器(Container) 用光盘装出来的系统 镜像跑起来的实例,可以起很多个
卷(Volume) 移动硬盘 持久化数据,容器删了数据还在
端口映射 前台转接 把容器内部端口暴露到宿主机

直接上例子,跑一个 Nginx 容器:

docker run -d \
  --name web \
  -p 8080:80 \
  -v web-data:/usr/share/nginx/html \
  --restart unless-stopped \
  nginx:latest

逐行拆解,这一行命令用到了全部四个概念:

  • -d:后台运行(detached)
  • --name web:容器起名 web,不然 Docker 会随机生成怪名字
  • -p 8080:80:端口映射,宿主机的 8080 → 容器内的 80。浏览器访问 http://服务器IP:8080 就能看到页面
  • -v web-data:...:卷,容器里的网站目录挂载到名为 web-data 的卷上,容器删了重建,网页文件都还在
  • --restart unless-stopped:服务器重启后容器自动拉起,自托管必备

验证:

docker ps                 # 看运行中的容器
curl http://localhost:8080

往卷里写个首页,刷新浏览器立即生效:

echo '<h1>Hello Docker</h1>' | sudo tee /var/lib/docker/volumes/web-data/_data/index.html

第三步:日常管理命令

docker ps              # 运行中的容器
docker ps -a           # 包括已停止的
docker logs -f web     # 实时看日志(相当于 tail -f)
docker exec -it web bash   # 进容器里操作(相当于 SSH 进集装箱)
docker stop web        # 停止
docker start web       # 启动
docker rm web          # 删除(需先停止)
docker images          # 看本地镜像
docker pull nginx:latest   # 拉取/更新镜像

更新容器的标准流程是:拉新镜像 → 删旧容器 → 用同样的 docker run 参数重建。数据在卷里,所以重建无损。这就是为什么自托管服务一定要挂卷。

第四步:compose——把命令写成配置文件

容器一多,docker run 后面跟一长串参数很难维护。Docker Compose 把这些参数写进一个 YAML 文件,一键起停整组服务:

mkdir -p ~/docker/web && cd ~/docker/web
nano compose.yaml
services:
  web:
    image: nginx:latest
    container_name: web
    ports:
      - "8080:80"
    volumes:
      - web-data:/usr/share/nginx/html
    restart: unless-stopped

volumes:
  web-data:

操作就三条:

docker compose up -d      # 启动(配置文件有变动也会自动重建)
docker compose ps         # 看状态
docker compose down       # 停止并删除容器(卷里的数据保留)

以后你在网上看到的自托管教程,绝大多数都是给一个 compose.yaml 文件——学会这个格式,全世界的自托管服务你都能装。

常见坑

现象 原因与解决
permission denied 连不上 docker 没加 docker 用户组,或加了之后没重新登录
拉镜像超时 网络问题,配代理或镜像加速器
端口冲突 port is already allocated 宿主机端口被占,ss -tlnp 查一下,换个端口映射
容器重启后数据没了 没挂卷;只删容器不删卷数据就在
docker compose 提示命令不存在 老版本是 docker-compose(带横杠),新版是插件形式 docker compose

小结

Docker 入门就是这么多:装引擎 → 理解镜像/容器/卷/端口 → 会跑会管 → 用 compose 写配置。从这篇开始,我的博客后续文章里凡是部署新服务,都会默认用 Docker 或 compose 的方式给出配置。

下一篇换个方向:给之前部署的 Nginx 站点挂上 HTTPS,把浏览器那个「不安全」警告消掉。