news 2026/9/29 15:14:19

Docker实战:从镜像容器到Docker Compose编排与排障

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Docker实战:从镜像容器到Docker Compose编排与排障

1. 写在前面:为什么我建议你认真学一下Docker

如果你最近两年一直在折腾服务器、部署应用、搭环境,那你大概率已经无数次撞见“Docker”这个关键字了。不管你是想在自己的Windows电脑上跑一个Redis主从,还是想把团队微服务项目一键拉起来,又或者是刚接触青龙面板、GitLab私有仓库甚至光猫网关MediaMTX这类偏门服务,Docker几乎成了绕不开的底层基建。

我第一次用Docker的时候,其实是带着情绪的:好好的MySQL装在服务器上不就行了吗?为什么要多套一层容器?直到我连续三次因为“在服务器上装了新东西把老环境搞挂”而通宵排查后,才算真正理解容器的价值——它是环境层面的“快照箱”:你装坏了大不了扔了重建,完全不碰宿主机。后来我买的服务器越来越多,从便宜的VPS到家里的NAS,Docker成了我部署任何项目的首选方案。

这篇内容会把我从入门到实战这几年踩过的坑整体梳理一遍,包括安装配置、基础命令、写Dockerfile打包自己的应用,以及网络、存储、日志这些日常最容易出问题的环节。我不会给你画一张庞大的概念地图,那只会让你更快放弃。我按“第一天用起来”的顺序来写,目标是:你照着做到最后,能把自己写的小项目打包成镜像,扔到一台干净的服务器上直接跑起来。

不管你是Windows用户还是纯Linux用户,这篇都照顾到了。Windows那边会重点说说Docker Desktop在Hyper-V、WSL2之间折腾的那些事;Linux这边说清楚命令行一把梭怎么最稳。好,我们直接开始。

2. 安装:别小看第一步,环境不对后面全是坑

2.1 Windows:Docker Desktop 的两种底子

Windows下目前没有选择余地,官方推荐的方案就是Docker Desktop,它底层支持两种模式:基于Hyper-V的虚拟机,和基于WSL2的后端。早年间只有Hyper-V一条路,现在微软把WSL2做得越来越顺手,导致很多Windows 10/11用户默认就走WSL2路线。

我个人的建议是:除非你在公司电脑上无法开启虚拟机平台,否则优先用WSL2模式。原因是WSL2的文件读写性能、启动速度、资源占用都明显优于Hyper-V方案,而且很多Linux下的Docker操作可以直接在WSL发行版里执行,调试起来更顺手。

但WSL2也有它的老毛病:第一,docker desktop 启动时如果报“Virtualization support not detected”“Docker Desktop failed to start because virtualization is disabled”这类错误,多半是BIOS里虚拟化开关没开,或者Windows的“虚拟机平台”功能没启用;第二,Windows防火墙有时会拦截WSL与宿主机的通信,导致宿主机浏览器访问容器端口失败。

我给你的保险操作流程是:先打开“控制面板 - 程序 - 启用或关闭Windows功能”,把“适用于Linux的Windows子系统”和“虚拟机平台”两项勾上,重启电脑;再进入BIOS确认Intel VT-x或AMD-V处于开启状态;然后安装WSL2内核更新包,最后再装Docker Desktop。任何一步漏了,后面都是连环报错。

安装完成后,Docker Desktop默认使用的WSL发行版如果没改过,一般是docker-desktop专用发行版。你别觉得这个概念复杂,可以这么理解:Docker引擎跑在Linux内核上,Windows通过WSL2分发内核来承载它,你所有命令其实是发送给这个“隐性Linux系统”的。

2.2 Linux:Ubuntu和CentOS的安装细节

如果是纯Linux服务器,命令行安装最省心。Ubuntu系列我建议直接走官方apt仓库:

sudo apt-get update sudo apt-get install ca-certificates curl gnupg lsb-release 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 a+r /etc/apt/keyrings/docker.gpg

然后写入源信息,这里注意系统版本代号要和lsb_release -cs保持一致:

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 sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

CentOS系则是:

sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo yum install -y docker-ce docker-ce-cli containerd.io sudo systemctl start docker sudo systemctl enable docker

如果你用的CentOS是7,且系统内核比较老,很可能会遇到docker服务启动失败的情况,报错信息五花八门,常见的是iptables相关或overlayfs相关。我遇到过CentOS7内核版本3.10下,老版本Docker直接起不来,升级内核后问题才消失。现在很多云服务器CentOS7默认跑的是3.10内核,这确实是痛点,建议要么换Ubuntu,要么升级内核。

Linux安装后还有一个高频问题:权限。每次执行docker命令都要sudo,很烦吧?把当前用户加入docker组,重新登录一下终端就好:

sudo usermod -aG docker $USER

但是——这里必须加个但是——这也意味着任何有docker组权限的用户都等同于root,因为docker本身就有提权风险。个人服务器这么干没问题,生产环境要有安全意识,别随意给用户加docker组。

2.3 权限和启动失败的通用排查

我自己生产环境踩过最典型的坑:docker服务启动失败,systemctl status docker看不到明确原因,翻日志才发现是磁盘满了。容器日志默认存放在/var/lib/docker/containers目录下,长时间不清理,镜像和日志能把磁盘撑爆。后面专门写日志和清理策略。

另一个常见启动错误是Ubuntu上apparmor与docker冲突,或SELinux状态不对。CentOS上如果SELinux处于enforcing模式,docker容器挂载目录时经常会权限异常,临时测试可以用setenforce 0,生产环境建议写好SELinux策略或改用Ubuntu。

3. 镜像、容器、仓库:把这三个概念焊死在脑子里

3.1 一张图理解它们的关系

很多人看教程一上来就敲docker run,敲是敲会了,遇到需要自定义镜像时彻底懵圈。本质上,你得先把三个概念的内外关系理清:

  • 镜像(Image):一个可执行的只读模板,类似虚拟机的“快照”或装系统的“ISO”。它包含操作系统基础层、运行环境、依赖库、应用代码和启动命令。
  • 容器(Container):镜像是模板,容器是模板的运行实例。就像类和对象的关系。同一个镜像可以同时跑多个容器,互不干扰。
  • 仓库(Registry):存放镜像的地方。Docker Hub是最大的官方仓库,也可以搭私有仓库(比如Harbor、或者简单用registry镜像)。

可以这么记:你拿一个ISO装出两台互不影响的虚拟机,ISO就是镜像,两台虚拟机就是容器。而“仓库”就是一堆ISO的光盘柜子。

3.2 常用命令:够用派,别背几十条

我见过很多人在入门时拿命令表背,我明确告诉你没必要。日常操作高频命令我帮你筛选好了:

# 拉镜像 docker pull nginx:latest # 跑容器(前台/后台) docker run -d --name web -p 8080:80 nginx docker run -it --rm ubuntu:22.04 bash # 查看容器和镜像 docker ps -a docker images # 进入运行中的容器 docker exec -it web bash # 日志 docker logs -f web # 删除 docker rm -f web docker rmi nginx:latest # 构建镜像 docker build -t my-app:v1 . # 组合管理 docker-compose up -d

不要被-d、-it、--rm这些参数吓到,用多了自然形成肌肉记忆。先掌握这一套,再需要高级参数时现查即可。

3.3 拉镜像慢、拉不动、超时怎么破

只要是国内服务器,第一天拉镜像大概率遇到“pull access denied”“timeout”或者慢到怀疑人生。这不是你操作问题,是网络原因。

解决方案很简单:配置镜像加速器。这是很多教程不写透的地方。以docker desktop为例,你在Settings的Docker Engine里找到JSON配置,加一段:

{ "registry-mirrors": [ "https://docker.m.daocloud.io", "https://dockerproxy.com" ] }

Linux上直接编辑/etc/docker/daemon.json,改完重启docker:

sudo systemctl daemon-reload sudo systemctl restart docker

需要注意:镜像加速器只能加速拉取,不能解决所有仓库访问异常,部分企业内部私有仓库需要额外配置insecure-registries,加在daemon.json的同一个文件里:

{ "insecure-registries": ["192.168.1.100:5000"] }

4. 实战案例一:用Docker安装Redis主从

4.1 为什么拿Redis主从练手

Redis主从应该是刚入门时性价比最高的练习项目。它既有配置文件、端口映射、数据持久化,又涉及多容器之间的网络通信,但整体又足够简单,不太会出现定位不了的玄学错误。

我在热搜词里看到很多人搜“docker安装redis主从”,说明大家在实际工作里确实需要这一套。我们这里用最标准的两个节点方案:一个主节点master,一个从节点slave。

4.2 完整操作步骤

首先建一个工作目录,把配置和持久化数据放外面,方便修改与备份:

mkdir -p ~/redis-cluster/data cd ~/redis-cluster

创建主节点redis.conf:

port 6379 appendonly yes appendfilename "appendonly.aof" requirepass yourpassword

然后运行主容器:

docker run -d \ --name redis-master \ -p 6379:6379 \ -v ~/redis-cluster/data:/data \ -v ~/redis-cluster/redis-master.conf:/etc/redis/redis.conf \ redis:7.2 redis-server /etc/redis/redis.conf

从节点的配置关键在replicaof这一行:

port 6379 replicaof 172.17.0.2 6379 masterauth yourpassword appendonly yes

这里要注意,replicaof后面的IP不能写127.0.0.1,因为容器之间通信走的是Docker内部网络。你先不要急着自己猜IP,跑一下命令看主容器的IP:

docker inspect redis-master | grep IPAddress

把输出的IP填进从节点配置,再跑从容器:

docker run -d \ --name redis-slave \ -p 6380:6379 \ -v ~/redis-cluster/slave-data:/data \ -v ~/redis-cluster/redis-slave.conf:/etc/redis/redis.conf \ redis:7.2 redis-server /etc/redis/redis.conf

进入主节点写数据,再到从节点查数据,能同步就说明这套链路通了:

docker exec -it redis-master redis-cli -a yourpassword set name "docker-redis" docker exec -it redis-slave redis-cli -a yourpassword get name

4.3 我踩过的坑和关键心得

第一,容器重启后IP会变化。上面直接写死IP的做法只适合实验环境。生产环境应该用Docker网络的自定义网桥,或者用docker-compose让服务名互相解析。后面docker-compose章节我再展开。

第二,从节点默认是只读的,这个不是配置错了,是设计如此。

第三,很多人挂了配置文件却启动失败,多半是容器里的redis用户没权限读取挂载文件,或者是文件权限不对。最常见错误是Windows下编辑的配置文件带有CRLF换行符,Linux容器加载时报错。解决方法很简单:用dos2unix转换一下,或者直接用VS Code设置换行符为LF再保存。

第四,如果你想在Windows的Docker Desktop里跑这套,注意把-p 6380:6379改成-p 63790:6379这种高位端口,避免Windows本机端口冲突。

5. 实战案例二:MySQL 8.0部署和常见失败排查

5.1 一条命令跑起来,但要考虑好数据目录

MySQL比起Redis要“重”不少,光环境变量和初始化就要小心。最简启动:

docker run -d \ --name mysql8 \ -e MYSQL_ROOT_PASSWORD=root123 \ -e MYSQL_DATABASE=testdb \ -p 3306:3306 \ -v ~/mysql-data:/var/lib/mysql \ mysql:8.0

这里关键在于-v ~/mysql-data:/var/lib/mysql:MySQL数据文件直接写到宿主机目录,容器删了数据还在,否则容器一删数据全没。

但是很多人在这一步就踩坑了。如果你把-v挂载到一个空目录,MySQL初始化时会检查权限,容器里mysql用户UID是999,宿主机目录如果是root用户创建,权限不对会导致无法写入,报错一般是“chown: changing ownership of '/var/lib/mysql/': Permission denied”或“mysqld: Can't change ownership of database directory”。

解决办法:先给目录授权再启动:

sudo chown -R 999:999 ~/mysql-data

或者更简单,直接用命名的volume,让docker自己管理权限:

docker volume create mysql-data docker run -d --name mysql8 -e MYSQL_ROOT_PASSWORD=root123 -v mysql-data:/var/lib/mysql -p 3306:3306 mysql:8.0

5.2 宿主连接、字符集和双重端口问题

MySQL8默认鉴权插件是caching_sha2_password,如果你用老版本navicat或PHP5连接,极容易报“Authentication plugin 'caching_sha2_password' cannot be loaded”。这种情况我建议在启动命令里加参数:

--default-authentication-plugin=mysql_native_password

或者进入容器后改用户:

ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'root123'; FLUSH PRIVILEGES;

还有字符集问题。许多人跑起来后数据写入正常,但中文变问号,大概率是容器默认字符集不是utf8mb4。启动参数补上:

--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci

另外一个容易忽略的点:如果本机3306端口已经被原来的MySQL占用,docker run会失败,日志显示“port is already allocated”。很多人第一时间怀疑镜像错了,其实查一下端口占用就明白。

5.3 容器重启后数据还在不在

这个问题问的人很多,我讲清楚:如果你的MySQL容器删除后重新run,并且用的是同一个数据卷,数据都还在。但如果你用docker logs看到的是全新初始化的日志,说明你没挂载对。

测试方法:在容器里建一张表插数据,然后docker rm -f mysql8,再重新docker run一遍挂同一个卷,查表,数据应该还在。这一步建议所有人亲手试一次,建立起“数据放卷里,容器随便删”的直觉。

6. Docker网络不通:排查思路和解决路径

6.1 容器通信常见的三大类原因

既然走到部署微服务或多容器的阶段,网络几乎必然出问题。“docker网络不通”是热搜词,我见过的问题集中在三类:

第一类是容器与宿主机端口映射不通。宿主机浏览器访问http://localhost:8080打不开,要依次排查:容器是否真的在监听8080、docker ps是否正确显示端口映射0.0.0.0:8080->80/tcp、防火墙是否放行。

第二类是容器与容器之间不通。比如Redis主从、微服务调用数据库,都是这一种。常见原因是两个容器在没有自定义网络时各自使用默认bridge,互相用IP访问时会因为IP变化或网络隔离而失效。解法是创建自定义网络:

docker network create app-net docker run -d --name mysql8 --network app-net ... docker run -d --name backend --network app-net ...

处于同一自定义网络的容器可以用容器名互相访问。backend容器里连MySQL,直接写mysql8:3306即可,不用记IP。

第三类是容器访问外网不通。容器里curl百度超时、apt update失败。大概率是宿主机的DNS配置在容器里继承出了问题。解决方案:运行容器时增加dns参数:

docker run -d --dns 223.5.5.5 ...

或在daemon.json里配置全局DNS。

6.2 一个真实排障案例

有一次我的Nginx容器反代后端服务,访问时总是502 bad gateway,日志又显示连接被拒绝。我把后端容器的端口映射关了(因为不想暴露外部),只用自定义网络通信,结果忘记把Nginx的配置从localhost:8080改成后端服务名:8080。折腾了半小时才发现。

教训很直接:默认bridge网络下容器之间只能用IP访问,自定义网络下才可以用容器名。你如果看到502/Connection refused,先看配置里“地址”部分,别急着怀疑容器挂了。

6.3 端口映射的特殊情况

kali搭建DVWA靶场、青龙面板、kodbox这类需要Web访问的场景,端口映射看起来不难,但有个细节:如果你用docker run -p 80:80做映射,实际是监听所有网卡。如果只想本机访问,更安全的写法是localhost:80:80或127.0.0.1:80:80。

还有一类坑是IPv6网络环境。如果你在云主机上配置了IPv6,容器默认可能只监听IPv4,导致IPv6请求无法访问。这时候建议在启动参数里追加-p [::]:80:80/tcp映射,或者直接关闭IPv6。

7. Dockerfile:把应用打包成标准交付件

7.1 为什么必须会写Dockerfile

很多教程教完run就会让人下载现成镜像跑起来,但真实项目中你迟早要面临一个问题:我写的PHP项目、Java SpringBoot服务、Python爬虫,怎么变成别人一条命令就能跑起来的镜像?

这正是Dockerfile的价值:把环境配置、依赖安装、代码拷贝、启动命令全部写进一个文本文件,任何机器上构建出的镜像行为一致。这也是“Docker部署微服务项目”的基础。

7.2 一个最小可用的Node.js示例

假设你有一个简单的Node应用,项目目录里包含app.js、package.json。多阶段构建是最好的实践:

# 第一阶段:构建 FROM node:18-alpine AS build WORKDIR /app COPY package*.json ./ RUN npm install COPY . . # 第二阶段:运行 FROM node:18-alpine WORKDIR /app COPY --from=build /app ./ EXPOSE 3000 CMD ["node", "app.js"]

很多人新手会疑惑:为什么需要两个FROM?因为最终镜像不应该包含源码编译时需要的多余文件、缓存、工具包,多阶段构建能极大减小镜像体积。这个习惯越早培养越好。

7.3 Java和PHP打包的其他形式

Java生态最常见的打包方式是:先用Maven或Gradle在宿主机打出jar包,再写Dockerfile拷贝jar。如果你的Maven环境没配置好,也可以用maven镜像先完成打包再拷贝,省得在宿主机装JDK和Maven:

FROM maven:3.8-openjdk-11 AS build WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn clean package -DskipTests FROM openjdk:11-jre-slim WORKDIR /app COPY --from=build /app/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]

PHP项目则要看是传统Apache/Nginx还是PHP-FPM架构。简单场景直接FROM php:8.2-apache,把代码拷贝进/var/www/html即可;复杂一点要装扩展,就得写RUN docker-php-ext-install pdo_mysql之类,这也是PHP里专门用docker部署时最容易卡住的地方,因为PHP官方镜像把编译扩展的命令封装成了docker-php-ext-install,新手不知道。

7.4 .dockerignore和构建缓存

我见过很多人在打完包后镜像体积巨大或意外包含敏感文件,都是因为没写.dockerignore。它的作用类似.gitignore,避免把node_modules、target、.git、.env等文件拷进构建上下文:

node_modules target .git .env *.log

还有一个关键机制是构建缓存。Dockerfile中每条指令如果没变化,会复用之前的缓存层。这就是为什么我习惯先COPY package*.json再RUN npm install,再COPY其他文件。只要依赖文件没变,即使业务代码改了几次,构建时npm install仍然走缓存,节省大量时间。

7.5 ENTRYPOINT和CMD的区别

热搜词里有“docker中的enterpoint命令和run命令”,我想这个指的是ENTRYPOINT和CMD。区别很简单:

  • CMD:容器启动默认执行的命令,但docker run后面跟参数时可以覆盖它。
  • ENTRYPOINT:容器的固定入口,不容易被docker run的参数覆盖,适合固定不可变的启动命令。

最佳实践是组合使用:ENTRYPOINT指定主程序,CMD提供默认参数。效果上类似:

ENTRYPOINT ["nginx"] CMD ["-g", "daemon off;"]

这样运行时你还能追加参数docker run ... -g "daemon off;",但不会替换掉nginx本身。

8. Docker Compose:多容器编排的救命稻草

8.1 为什么有了docker run还要Compose

单个容器用docker run完全OK,但一旦涉及微服务项目,前端、后端、数据库、缓存、消息队列,你要写好几条docker run命令,还要保证启动顺序、网络连通、数据卷一致。人会疯的。

docker-compose.yml把服务、网络、卷、环境变量声明式地写在同一个文件里,一条docker compose up -d全部启动。对我这种习惯把整个项目配置托管到Git仓库的人来说,这才是真正可复现的部署单元。

8.2 一个标准的Compose文件长什么样

以最常见的应用+MySQL为例:

services: app: build: . container_name: myapp ports: - "8080:8080" environment: SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/testdb?useSSL=false SPRING_DATASOURCE_USERNAME: root SPRING_DATASOURCE_PASSWORD: root123 depends_on: - db restart: unless-stopped db: image: mysql:8.0 container_name: mysqldb environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: testdb volumes: - db_data:/var/lib/mysql ports: - "3306:3306" restart: unless-stopped volumes: db_data:

注意两个细节:一是app连接数据库的地址写的是db而不是localhost,因为在同一个compose网络里,服务名就是主机名;二是depends_on能保证db先启动,但它只保证“容器起来了”,不代表MySQL初始化完成。如果要更严谨,需要健康检查healthcheck配置。

8.3 青龙面板这类多组件项目如何套Compose

现在很流行的青龙面板在Docker中部署,实际是把几个服务组合在一起:面板本身、依赖的数据库、消息推送、运行环境。我看热搜里有“docker青龙依赖管理”,很多人手工部署时控制依赖很痛苦,但用compose就能统一环境变量。

其实不只是青龙,任何你看到“docker run一长串参数”的镜像,都可以改写为compose配置,然后项目交接的时候把那份yaml发给别人,他直接复现同样的环境。这是为什么Compose值得专门学习。

8.4 Compose项目升级和迁移

当你的项目要挪到新服务器时,别傻到手动重新创建容器。正确做法:把docker-compose.yml、环境变量文件、数据卷备份拷到新机器,执行docker compose up -d,几乎零迁移成本。

这里有个惨痛教训:如果你做数据卷的时候用的是bind mount(比如/home/user/data),迁移时必须同步拷贝那个目录;如果你用的命名卷,需要docker run --volumes-from或使用专用迁移工具,否则数据全丢。

9. 青龙面板部署与依赖管理(延展案例)

9.1 青龙面板是什么,为什么火

青龙面板是一个定时任务管理平台,常被用来跑各类定时脚本、签到脚本等,支持Python、JavaScript、Shell多种脚本语言。因为功能清爽、可视化好,很多开发者把它部署在服务器或软路由上,用来管理定时任务。注意我在这里不展开具体脚本内容,只讲Docker部署本身。

官方最简部署方式:

docker run -d \ --name qinglong \ -p 5700:5700 \ -v ~/qinglong/data:/ql/data \ --restart unless-stopped \ whyour/qinglong:latest

访问http://宿主机IP:5700即可通过Web界面配置任务。

9.2 依赖管理:我看到的“依赖”痛点

依赖管理是青龙面板部署里的麻烦点。面板本身提供一个“依赖管理”功能,可安装Python、Node、Linux相关的依赖包,但新手经常遇到“依赖安装失败”或“脚本运行提示找不到模块”。

这里要明白一个概念:运行脚本的Python或Node环境在容器内部。你在宿主机pip install的包对容器内脚本毫无用处。必须在面板的依赖管理里安装,或进入容器手动装:

docker exec -it qinglong bash pip3 install requests

但更稳的做法是:把依赖声明写进compose或Dockerfile的构建过程,避免每次容器重建后都要重复安装。

9.3 青龙升级注意

升级青龙如果要保留数据,务必提前备份/ql/data目录。有些版本跨版本升级后数据库格式变化,直接大版本跳跃可能出问题。我的习惯是升级前先打一个数据卷快照或直接把整个data目录压缩存一份,多花两分钟,出事后能救命。

10. 仓库管理:从Docker Hub到私有仓库

10.1 Docker Hub真的够用吗

Docker Hub是公共镜像仓库,但有两个问题:一是网络问题明显,二是企业内或私有代码库不能直接推上去。所以了解私有仓库配置很有必要。

最轻量级的私有仓库就是用registry镜像:

docker run -d \ --name registry \ -p 5000:5000 \ -v ~/registry-data:/var/lib/registry \ registry:2

推镜像前需要给镜像打上私有仓库地址标签:

docker tag my-app:v1 localhost:5000/my-app:v1 docker push localhost:5000/my-app:v1

其他服务器拉取时,需要在该服务器的daemon.json中配置insecure-registries,要不然HTTPS证书校验会一直报错。

10.2 镜像命名和Tag管理技巧

很多团队镜像管理混乱,因为没有约定Tag规范。我的建议是至少包含版本号和构建时间或Git提交号,比如my-app:20250215-abc1234。不要只用latest,生产环境用latest是灾难,因为你不知道拉下来的是哪天的版本。

10.3 从GitLab CI到Docker仓库的自动化

如果你已经在用GitLab,社区版也可以启用GitLab Runner,结合gitlab-ci.yml实现代码提交后自动构建镜像并推送到私有仓库。这一套组合还是目前中小团队最务实的落地路径。

GitLab社区版的Docker部署本身就依赖容器,部署和升级路径都比较成熟。部署时注意内存要充足,至少给4G,否则GitLab经常被OOM干掉,这应该是全员共识了。

11. 常见问题与报错速查表

11.1 启动类报错

我在热搜词里看到好几个具体报错,这里一个个说清楚:

  • Docker Desktop failed to start because virtualization is disabled:这是Windows下最典型的提示。核心意思是虚拟化没开或Windows功能没启用。按我前面2.1节说的三步走,90%能解决。
  • failed to connect to the docker api at npipe:////./pipe/dockerdesktoplinuxengine:Windows下常见的“连不上Docker引擎”错误。一般是Docker Desktop没有真正启动成功,或WSL发行版没起来。解决办法是重启Docker Desktop,或在PowerShell里执行wsl --shutdown后重新start。
  • Cannot connect to the Docker daemon at unix:///var/run/docker.sock:Linux下cmd执行时最常见的报错。原因是docker服务没启动,或者当前用户无权限访问socket。先systemctl start docker,再确认是否在docker组。

11.2 拉取与构建类报错

  • manifest unknown:拉取镜像时tag名写错,或私有仓库里不存在该tag。
  • denied: requested access to the resource is denied:可能是镜像不存在,也可能是Docker Hub上权限不足,检查登录状态docker login。
  • no space left on device:磁盘满了,用docker system prune清理,但注意这个命令会删除所有未被容器使用的镜像、网络和构建缓存,请谨慎。

如果只想清理日志和已停止容器,用docker container prune更安全。

11.3 运行类报错

  • port is already allocated:端口占用,netstat -tulnp检查。
  • standard_init_linux.go: exec user process caused "exec format error":通常是架构不对。你在x86服务器上构建的镜像推到ARM设备(比如树莓派、部分NAS)会报这个错误。用docker buildx构建多架构镜像解决。
  • permission denied while trying to connect to the Docker daemon socket:Linux权限问题,参照2.2节加入docker组。
  • OCI runtime exec failed:执行docker exec时容器内没有bash,改用sh,或者容器本身已挂掉。

11.4 自检清单与调试习惯

排查问题一定按顺序来:先docker ps确认容器状态,再看docker logs,最后网络测试。不要上来就猜配置。很多看起来玄学的问题,docker logs都会给出真正的线索。

有一个小习惯我很推荐:加个-p参数不一定有用,但在容器里执行命令时总能看到更多细节。比如容器内部访问网络,可以用docker exec -it 容器名 curl 目标地址,立刻区分是容器内问题还是映射问题。

12. 资源限制与优雅启停:生产环境必备素养

12.1 内存和CPU限制

Docker容器不加限制时能用完宿主机所有资源。多个容器互相争抢导致宿主机崩溃的案例并不少。我建议所有生产容器都加限制,至少内存限制:

docker run -d --memory="512m" --cpus="1.5" ...

Compose里同样配置:

services: app: deploy: resources: limits: cpus: "1.5" memory: 512M

限制并不难理解,它的意义在于防止一个容器拖垮整台服务器。

12.2 优雅停止和重启策略

docker stop默认等待10秒后kill,如果应用需要完成事务,设置更长的停止超时:

docker stop -t 30 container_name

容器在接收SIGTERM后应该自行处理收尾,这对Java应用尤其重要,因为Spring Boot等框架需要时间优雅关闭。

重启策略有四种级别,我推荐多数服务用unless-stopped:这样即使服务器重启后docker自动拉起容器,但你手动停掉容器后它不会自动再启动。

生产环境请设置日志轮转,否则日志文件无限增长。方法是在daemon.json里加:

{ "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }

13. 结尾:最后说点实在话

学了这么多,最后聊一聊我的个人体会。Docker真正改变的不是技术栈本身,而是部署心态。以前我改个配置要在服务器上小心翼翼,生怕搞崩环境;现在我改坏了大不了docker rm -f再重新run,镜像变了就重新build。这种“环境可以随便扔”的底气,只有真正把镜像、卷、容器这三层关系用顺了之后才能体会到。

如果你接下来准备自己动手练,我建议一条最直接的学习路径:先把官方Docker Hub里的Nginx镜像跑起来,改默认页面,看看宿主机浏览器能不能访问;然后写一个最简单的Node或Python应用,打包成镜像,放到另一台干净服务器上运行;最后把两个服务用docker-compose编排起来,连上数据库,模拟一次微服务调用。走完这三步,日常工作中90%的Docker场景你就能应付了。

我现在自己上手的习惯是:任何部署项目,先写compose文件和.env配置,把密码、端口、数据卷都固定好,再一键拉起。这套方式花的时间可能比手动run多条命令多一点,但维护收益是几何级数增长的。

希望这篇能把Docker这个工具讲得“能落地”而不是“概念好看”。有任何问题,欢迎在评论区带着你的报错信息来找我,我大概率也踩过同一个坑。

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

Java校园垃圾分类管理系统实战:从需求到部署的完整落地指南

简介:这份资源是面向高校计算机专业学生与Java Web开发学习者的校园垃圾分类管理系统毕业设计文档,采用SSM框架、Java技术与MySQL数据库实现,适合作为课程设计、毕设选题或SSM入门练手项目的参考方案。压缩包内仅含1个docx文档,大…

作者头像 李华
网站建设 2026/9/29 15:10:40

思科IE3000工业交换机:选型、配置与故障排查实战指南

简介:思科IE3000系列工业以太网交换机产品样本,适合工业网络规划与运维人员、自动化工程师及系统集成商参考,用于产品选型评估与恶劣工业环境下的网络基础设施设计。资料为单份docx产品文档,约70KB,内容涵盖产品概述、…

作者头像 李华
网站建设 2026/9/29 15:10:20

Java面试总挂?这5个坑你肯定踩过

面试又挂了。简历投了几十份,笔试过了不少,可一到技术面就卡壳。你开始怀疑自己:是不是技术太差?是不是运气不好?其实,很多时候不是你能力不行,而是踩了面试的“隐形坑”。这些坑,几…

作者头像 李华
网站建设 2026/9/29 15:10:18

SpringBoot全局异常处理,优雅到极致

在SpringBoot项目中,异常处理是绕不开的话题。如果每个Controller都写一堆try-catch,代码会变得臃肿不堪;如果直接把异常堆栈抛给前端,用户体验和系统安全都会大打折扣。真正优雅的做法,是让异常处理集中化、标准化、可…

作者头像 李华
网站建设 2026/9/29 15:09:02

通州工商变更公司筛选名录 资质齐全机构推荐

在北京经营企业,常会遇到需要调整工商登记信息的情况,很多通州本地的经营者都会搜索相关问题,我们整理了三个大家最关心的问题,逐一给大家解答。Q1:通州本地办理工商变更,为什么要找本地化的机构代办?在北…

作者头像 李华