运行你的第一个Docker容器

一条docker run启动Nginx,一条docker run启动MySQL------这篇搞懂容器运行、管理和排障的核心操作,顺带避开"容器跑着跑着就退了"这个初学者必踩的坑。

docker run------从一条命令说起

docker run是Docker的核心命令,作用是把镜像变成正在运行的容器。先看最简单的用法:

bash 复制代码
# 最简形式:拉取nginx镜像并启动
docker run nginx:1.27

# 但这样运行有个问题------容器占着你的终端
# 加 -d 让它后台运行
docker run -d nginx:1.27

按场景掌握参数

docker run有几十个参数,但日常常用的就这几个,按场景比较容易记:

场景 参数 作用 示例
后台运行 -d 容器在后台跑,不占终端 docker run -d nginx:1.27
给容器起名 --name 方便管理,不然得记ID docker run --name myweb nginx:1.27
端口映射 -p 宿主机:容器 外部访问容器内的服务 docker run -p 8080:80 nginx:1.27
环境变量 -e KEY=VALUE 向容器传配置 docker run -e MYSQL_ROOT_PASSWORD=secret mysql:8.4
交互式终端 -it 进入容器内操作 docker run -it ubuntu:24.04 bash
自动清理 --rm 容器停止后自动删除 docker run --rm hello-world
重启策略 --restart 容器挂了自动重启 docker run --restart unless-stopped nginx:1.27

💡 -v数据卷挂载和--network网络配置也很常用,但内容较多,咱们后面再深入讲解。

镜像名和标签

bash 复制代码
# ❌ 禁止:不指定标签,默认用latest
docker run nginx

# ✅ 正确:明确指定版本号
docker run nginx:1.27
docker run mysql:8.4
docker run redis:8.0-alpine

latest不等于"最新稳定版"------它只是一个标签名,镜像维护者想指向哪个版本就指向哪个版本。某天latest从8.4跳到9.0,你的环境就炸了。永远明确指定版本号。


容器生命周期------主进程活着容器就活着

理解容器的生命周期,关键记住一句话:主进程(PID 1)活着,容器就活着;主进程退出,容器就停止。

sql 复制代码
docker create    docker start    docker pause    docker stop    docker rm
    │                │               │               │              │
    ↓                ↓               ↓               ↓              ↓
┌────────┐      ┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐
│Created │ ───→ │ Running │ ─→ │ Paused  │ ─→ │ Stopped │ ─→ │ Removed │
└────────┘      └────┬────┘    └────┬────┘    └────┬────┘    └─────────┘
                     │              │              │
                     │  docker      │  docker      │  docker
                     │  restart     │  unpause     │  start
                     ↓              ↓              ↓
                 ┌─────────┐    ┌─────────┐    ┌─────────┐
                 │ Running │    │ Running │    │ Running │
                 └─────────┘    └─────────┘    └─────────┘
状态 说明 触发方式
Created 容器已创建但未启动 docker create
Running 主进程运行中 docker run / docker start
Paused 进程冻结(内存保留) docker pause
Stopped 主进程已退出 docker stop / 主进程退出
Removed 容器被删除 docker rm

为什么容器一启动就退出? 这是初学者最高频的问题。常见原因:

bash 复制代码
# 原因1:主进程不是前台运行
# Nginx默认fork子进程后父进程退出,容器跟着停了
docker run nginx:1.27  # 容器立即退出

# 原因2:一次性任务执行完就退出
docker run ubuntu:24.04 echo "hello"  # 输出后容器退出,exit code 0

# 原因3:配置错误导致主进程崩溃
docker run -e MYSQL_ROOT_PASSWORD="" mysql:8.4  # 密码不能为空

排查方法:

bash 复制代码
# 1. 看容器状态和退出码
docker ps -a
# EXIT CODE 列:0=正常退出, 1=应用错误, 137=OOM, 143=被stop

# 2. 看日志
docker logs 容器名

# 3. 用交互模式调试(覆盖默认命令)
docker run -it --entrypoint bash mysql:8.4

💡 退出码速查:0 =正常结束 | 1 =应用错误 | 137 =内存超限被Kill | 143=SIGTERM优雅退出。后续会深入讲解所有退出码。


容器管理命令

光会docker run还不够,日常管理需要这些命令:

查看

bash 复制代码
# 列出运行中的容器
docker ps

# 列出所有容器(包括已停止的)
docker ps -a

# 自定义输出格式
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"

启停和删除

bash 复制代码
# 停止容器(发送SIGTERM,10秒后SIGKILL)
docker stop 容器名

# 启动已停止的容器
docker start 容器名

# 重启容器
docker restart 容器名

# 删除已停止的容器
docker rm 容器名

# 强制删除运行中的容器
docker rm -f 容器名

# 批量清理已停止的容器
docker container prune

日志和调试

bash 复制代码
# 实时跟踪日志
docker logs -f 容器名

# 只看最后50行
docker logs --tail 50 容器名

# 查看最近5分钟的日志
docker logs --since 5m 容器名

# 进入容器执行命令(推荐方式)
docker exec -it 容器名 bash
# Alpine镜像没有bash,用sh
docker exec -it 容器名 sh

# 在容器内执行单条命令(不进入容器)
docker exec 容器名 cat /etc/os-release

⚠️ exec vs attach :初学者只用docker execdocker attach连接的是主进程(PID 1),你敲exit容器就停了。docker exec创建新进程,exit不影响容器运行。

检查和监控

bash 复制代码
# 查看容器详细信息(JSON格式)
docker inspect 容器名

# 格式化输出常用字段
docker inspect --format '{{.State.Status}}' 容器名          # 运行状态
docker inspect --format '{{.State.ExitCode}}' 容器名        # 退出码
docker inspect --format '{{.State.OOMKilled}}' 容器名       # 是否OOM
docker inspect --format '{{.RestartCount}}' 容器名          # 重启次数
docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' 容器名  # IP地址

# 实时资源监控
docker stats

# 单次快照(不持续刷新)
docker stats --no-stream

# 查看容器内进程
docker top 容器名

--restart重启策略

生产环境的容器挂了不能靠人工重启,--restart参数让Docker自动帮你处理:

策略 行为 推荐场景
no(默认) 不重启 一次性任务
on-failure[:N] 非零退出码时重启,最多N次 有重启循环风险的服务
always 任何情况都重启 ⚠️ 注意:手动stop后daemon重启还会拉起来
unless-stopped 除非手动stop,否则重启 生产首选
bash 复制代码
# 生产环境推荐
docker run -d --restart unless-stopped --name myweb -p 8080:80 nginx:1.27

# 限制重启次数(防止无限循环)
docker run -d --restart on-failure:5 --name myapp myapp:1.0

⚠️ always的坑 :手动docker stop后,如果Docker daemon重启(比如服务器重启),always策略的容器会重新启动。而unless-stopped会尊重你的手动停止操作。所以生产环境优先选unless-stopped

Docker重启采用指数退避:100ms → 200ms → 400ms → ...,上限1分钟。容器成功运行10秒后重置退避计数器。


实战一:运行Nginx Web服务器

bash 复制代码
# 1. 后台运行Nginx,映射端口,指定版本
docker run -d --name mynginx -p 8080:80 --restart unless-stopped nginx:1.27-alpine

# 2. 确认运行状态
docker ps
# CONTAINER ID   IMAGE              STATUS       PORTS                  NAMES
# a1b2c3d4e5f6   nginx:1.27-alpine  Up 3 seconds 0.0.0.0:8080->80/tcp   mynginx

# 3. 浏览器访问 http://localhost:8080,看到Nginx欢迎页

# 4. 用curl验证
curl http://localhost:8080

自定义页面

bash 复制代码
# 1. 创建本地HTML目录
mkdir -p ~/nginx-html

# 2. 写一个简单页面
cat > ~/nginx-html/index.html << 'EOF'
<!DOCTYPE html>
<html>
<head><title>My Docker Nginx</title></head>
<body>
  <h1>Hello from Docker! 🐳</h1>
</body>
</html>
EOF

cat > nginx-html/index.html << 'EOF'
<!DOCTYPE html>
<html>
<head><title>My Docker Nginx</title></head>
<body>
  <h1>Hello from Docker! 🐳</h1>
</body>
</html>
EOF

# 3. 重新运行,挂载本地目录到Nginx默认站点目录
docker stop mynginx && docker rm mynginx
docker run -d --name mynginx -p 8080:80 --restart unless-stopped \
  -v ~/nginx-html:/usr/share/nginx/html:ro \
  nginx:1.27-alpine

# 4. 刷新浏览器,看到自定义页面

-v ~/nginx-html:/usr/share/nginx/html:ro表示把宿主机目录挂载到容器内,:ro表示容器内只读。数据卷的完整用法见专栏相关文章。


实战二:运行MySQL数据库

bash 复制代码
# 运行MySQL 8.4(LTS版本)
docker run -d \
  --name mysql-dev \
  -p 3306:3306 \
  -e MYSQL_ROOT_PASSWORD=devpass123 \
  -e MYSQL_DATABASE=myapp \
  --restart unless-stopped \
  mysql:8.4

# 参数说明:
# -e MYSQL_ROOT_PASSWORD:必须设置,否则容器启动失败
# -e MYSQL_DATABASE:初始化时创建指定数据库

等待MySQL就绪

MySQL启动需要10-30秒,用日志确认就绪:

bash 复制代码
# 跟踪日志,等待 "ready for connections"
docker logs -f mysql-dev
# 看到 [System] [MY-010931] /usr/sbin/mysqld: ready for connections 就OK了

连接MySQL

bash 复制代码
# 方式1:进入容器内用mysql客户端
docker exec -it mysql-dev mysql -uroot -pdevpass123

# 方式2:用宿主机客户端(需要安装mysql-client)
mysql -h 127.0.0.1 -P 3306 -uroot -pdevpass123

连接后测试:

sql 复制代码
-- 查看数据库
SHOW DATABASES;

-- 使用初始化创建的库
USE myapp;

-- 创建表
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) NOT NULL,
    email VARCHAR(100)
);

-- 插入数据
INSERT INTO users (name, email) VALUES ('张三', 'zhangsan@example.com');

-- 查询
SELECT * FROM users;

-- 退出
EXIT;

数据持久化

⚠️ 默认情况下,MySQL数据存在容器内部,删容器=删数据! 挂载卷才能持久化:

bash 复制代码
docker stop mysql-dev && docker rm mysql-dev

# 挂载宿主机目录保存数据
docker run -d \
  --name mysql-dev \
  -p 3306:3306 \
  -e MYSQL_ROOT_PASSWORD=devpass123 \
  -e MYSQL_DATABASE=myapp \
  --restart unless-stopped \
  -v ~/mysql-data:/var/lib/mysql \
  mysql:8.4

⚠️ 环境变量只在首次初始化 (数据目录为空)时生效。如果~/mysql-data已经有数据,改-e参数不会生效。


实战三:运行Redis缓存服务

bash 复制代码
# 基础运行
docker run -d \
  --name redis-dev \
  -p 6379:6379 \
  --restart unless-stopped \
  redis:8.0-alpine

# 连接Redis
docker exec -it redis-dev redis-cli

连接后测试:

bash 复制代码
127.0.0.1:6379> SET mykey "Hello Redis!"
OK
127.0.0.1:6379> GET mykey
"Hello Redis!"
127.0.0.1:6379> SETEX token 3600 "abc123"   # 3600秒后过期
OK
127.0.0.1:6379> KEYS *
1) "mykey"
2) "token"
127.0.0.1:6379> EXIT

如果需要数据持久化,加上AOF配置:

bash 复制代码
docker run -d \
  --name redis-dev \
  -p 6379:6379 \
  --restart unless-stopped \
  -v ~/redis-data:/data \
  redis:8.0-alpine \
  redis-server --appendonly yes

端口映射与目录挂载要点

这两个操作在实战中高频使用,咱快速梳理要点:

端口映射(-p)

bash 复制代码
# 格式:-p 宿主机端口:容器端口
-p 8080:80              # 基本映射
-p 127.0.0.1:3306:3306  # 只允许本机访问
-p 8080:80 -p 8443:443  # 多个端口

常见误区:

  • -p 8080:80的意思是:访问宿主机的8080 → 转发到容器的80
  • 映射后外部仍可能访问不了,检查防火墙和云平台安全组

目录挂载(-v)

bash 复制代码
# 格式:-v 宿主机路径:容器路径[:ro]
-v ~/html:/usr/share/nginx/html      # 普通挂载
-v ~/html:/usr/share/nginx/html:ro   # 只读挂载
-v mydata:/var/lib/mysql             # 命名卷(Docker管理)

💡 端口映射的网络原理和容器间通信在网络部分深入,数据卷的三种挂载方式和命名卷管理在专栏其他文章详细讲解。


常见问题

容器一启动就退出了怎么办?

按顺序排查:

bash 复制代码
# 1. 看退出码
docker ps -a
# 0=正常退出, 1=应用错误, 137=OOM被杀, 143=被stop

# 2. 看日志
docker logs 容器名

# 3. 交互模式调试
docker run -it --entrypoint bash 镜像名

最常见的原因:主进程没有前台运行。Docker容器需要主进程保持前台,否则容器立即退出。

端口映射后外部访问不了?

bash 复制代码
# 1. 确认容器在运行且端口映射正确
docker ps
# 看PORTS列是否有映射关系

# 2. 本地先试
curl localhost:8080

# 3. 检查防火墙
sudo ufw status        # Linux
# 或云平台安全组规则

docker exec进去后exit容器也停了?

你用的是docker attach而不是docker exec

bash 复制代码
# ❌ 危险:exit会停止容器
docker attach 容器名

# ✅ 安全:exit不影响容器
docker exec -it 容器名 bash

如果一定要用attach,退出时按Ctrl+P, Ctrl+Q(不是exit)来分离。

环境变量修改后不生效?

MySQL等镜像的环境变量只在首次初始化 时生效。如果数据目录已有数据,改-e参数不会重新初始化。需要删除数据目录后重建容器,或者进入容器手动修改。

如何清理所有容器?

bash 复制代码
# 停止所有运行中的容器
docker stop $(docker ps -q)

# 删除所有容器(包括运行中的)
docker rm -f $(docker ps -aq)

# 或者分步清理
docker container prune   # 只删除已停止的

⚠️ 清理前确认没有未持久化的重要数据!


总结

  1. docker run核心参数按场景记:-d后台、--name命名、-p端口、-e环境变量、--restart重启策略
  2. 主进程=容器生命周期,主进程退出容器就停止------这是理解所有容器退出问题的钥匙
  3. 退出码速查:0正常 | 1应用错误 | 137 OOM | 143被stop
  4. docker exec -it进入容器,docker logs -f看日志,docker inspect查配置------排障三件套
  5. 永远指定镜像版本号 ,禁止用latest标签
  6. --restart unless-stopped是生产首选重启策略
  7. 重要数据必须挂载-v,否则删容器=删数据

实战演练

项目背景

你所在团队准备用Docker部署开发环境,需要在一台服务器上同时运行Nginx、MySQL、Redis三个服务,并确保数据持久化和自动重启。

功能需求

FR-001 运行Nginx容器 作为前端开发者,我希望运行Nginx容器并提供自定义静态页面,以便验证Web服务是否正常工作。 验收标准:

  • Given:Docker已安装且镜像加速器已配置
  • When:执行docker run启动Nginx,挂载自定义HTML目录
  • Then:curl localhost:8080返回自定义页面内容

FR-002 运行MySQL容器 作为后端开发者,我希望运行MySQL容器并创建初始数据库,以便微服务应用可以连接使用。 验收标准:

  • Given:Docker已安装且3306端口未被占用
  • When:执行docker run启动MySQL,设置root密码和数据库名
  • Then:docker exec连接MySQL后SHOW DATABASES能看到myapp数据库

FR-003 运行Redis容器 作为后端开发者,我希望运行Redis容器并开启持久化,以便缓存数据不会因容器重启丢失。 验收标准:

  • Given:Docker已安装且6379端口未被占用
  • When:执行docker run启动Redis并挂载数据目录
  • Then:docker exec连接Redis后SET testkey "hello"成功,重启容器后GET testkey仍返回"hello"

FR-004 验证重启策略 作为运维人员,我希望所有容器配置了自动重启策略,以便服务器重启后服务自动恢复。 验收标准:

  • Given:三个容器均配置了--restart unless-stopped
  • When:执行sudo systemctl restart docker
  • Then:docker ps显示三个容器均为Running状态

需求范围

In Scope :运行Nginx/MySQL/Redis容器、数据持久化、重启策略验证 Out Scope:Docker Compose编排、自定义网络通信、Dockerfile构建

约束条件

  • 所有镜像必须指定版本号,禁止latest
  • MySQL root密码不得为空
  • Redis必须开启AOF持久化
  • 所有容器配置--restart unless-stopped

依赖与前置条件

  • 已完成Docker环境已安装并配置镜像加速器
  • 端口8080、3306、6379未被占用

有问题评论区见!

相关推荐
渣波1 小时前
拒绝黑盒!NestJS + LangChain 实战保姆级拆解,手把手教你搞定双 Token 与 AI 大脑
前端·后端
MacroZheng1 小时前
斩获20w star!Claude Code最强插件,AI编程必备!
java·人工智能·后端
IT_陈寒1 小时前
Vite打包后的路径问题差点让我改了一天代码
前端·人工智能·后端
铁皮饭盒2 小时前
Bun 多线程有多快?postMessage 传输字符串比 Node.js 快 400 倍!
前端·javascript·后端
葫芦和十三3 小时前
图解 MongoDB 12|索引与查询优化地图:一条主线,三个判断轴
后端·mongodb·agent
葫芦和十三9 小时前
图解 MongoDB 11|慢查询排查闭环:从 Profile 到 explain 的分层路径
后端·mongodb·agent
葫芦和十三12 小时前
图解 MongoDB 09|explain 再读:从 queryPlanner 到 executionStats
后端·mongodb·agent
葫芦和十三12 小时前
图解 MongoDB 10|覆盖查询:让索引把活干完,根本不用回表
后端·mongodb·agent
大鸡腿同学14 小时前
从 CoT 思维链到 ReAct:智能 Agent 到底是怎么 “思考” 的?
后端