一、Docker 究竟解决了什么问题
传统部署模式下,环境不一致是头号痛点:开发机跑得好好的,一到服务器就"依赖缺失、端口冲突、版本对不上"。Docker 用镜像(Image)+ 容器(Container)把应用及其所有依赖打包成一个可移植的单元,在任何装了 Docker 的机器上都以同样方式运行。镜像分层、容器共享内核,比虚拟机更轻量,秒级启动。对 VPS 用户来说,最大的价值是"一键部署、快速隔离、回滚方便",你不再需要手工折腾某服务的依赖和配置文件。
二、核心概念:镜像、容器、仓库与 Dockerfile
四个词理清楚,Docker 就懂了大半:
- 镜像 Image:只读的模板,类似"安装包",如 nginx:latest、mysql:8.0。
- 容器 Container:镜像的运行时实例,可启动、停止、删除,彼此隔离。
- 仓库 Repository:存放和分发镜像的地方,默认 Docker Hub,也可自建私有仓库。
- Dockerfile:用指令描述的镜像构建脚本,决定镜像内容。
# 拉取nginx镜像并运行一个命名为 web 的容器,映射端口 80->8080,后台运行 docker pull nginx:latest docker run -d --name web -p 8080:80 nginx:latest # 查看运行中的容器、停止、删除、进入容器执行命令 docker ps docker stop web docker rm web docker exec -it web bash
三、Dockerfile 编写:从 0 构建一个 Web 应用镜像
以下是一个 Python Flask 应用的 Dockerfile,演示常见指令的用法:
# 基于官方 python 3.11 基础镜像 FROM python:3.11-slim # 设置工作目录 WORKDIR /app # 先复制依赖清单,利用镜像层缓存加快二次构建 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 再复制源码(比依赖改动更频繁) COPY . . # 暴露端口 EXPOSE 5000 # 指定容器启动时执行的命令 CMD ["gunicorn", "-b", "0.0.0.0:5000", "app:app"]
构建并运行:
docker build -t my-flask-app . docker run -d --name myapp -p 5000:5000 my-flask-app
写 Dockerfile 的三大要点:把 COPY 依赖清单放在源码 COPY 之前以利用层缓存;用 .dockerignore 排除 node_modules、.git、__pycache__ 等垃圾;生产镜像尽量用精简基础镜像(slim/alpine)缩小体积、减少攻击面。不要用 sudo docker 之外随意把整个目录 COPY 进去。
四、docker-compose:多服务一键编排
单容器只解决了单个进程,真实 Web 应用往往有 Web + 数据库 + 缓存多个服务。docker-compose 用一份 YAML 把多容器组织起来,一条 up -d 全部拉起。以 Nginx + PHP + MySQL 的 LNMP 栈为例:
# docker-compose.yml
version: "3.8"
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./www:/usr/share/nginx/html:ro # 挂载站点目录
depends_on:
- php
php:
image: php:8.2-fpm
volumes:
- ./www:/var/www/html
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: "strong-pass"
MYSQL_DATABASE: myapp
volumes:
- dbdata:/var/lib/mysql # 数据卷持久化
volumes:
dbdata: # 声明数据卷,容器删除数据仍在
启动:
docker-compose up -d # 后台启动全部服务 docker-compose ps # 查看状态 docker-compose logs -f # 聚合日志 docker-compose down # 停止并移除(-v 同时删数据卷,慎用)
这里 volumes 是关键——容器本身是无状态的,数据必须通过"数据卷/挂载目录"持久化,否则容器一删就数据全丢。生产环境务必把数据库目录挂到宿主机并纳入备份。
五、常用运维命令与镜像管理
docker images # 查看本地镜像 docker rmi nginx:latest # 删除镜像 docker logs -f myapp # 查看容器日志 docker stats # 实时查看容器资源占用 docker inspect myapp # 查看容器详细信息 docker system prune # 清理悬空镜像/停用容器/孤儿数据卷 docker exec -it myapp bash # 进入运行中的容器
磁盘紧张时,docker system df 可以看清镜像、容器、数据卷各占多少空间,结合 prune 回收。
六、常见坑与安全建议
新手最容易踩的坑和高频安全点:
- 端口映射冲突:
-p 80:80报端口被占用,先docker ps查是否已有容器占用,或改用其他宿主机端口。 - 容器重启后配置丢失:关键数据必须走 volumes 持久化,容器本身可随时重建。
- 不要以 root 运行容器内进程:Dockerfile 用
USER指定非 root 用户,降低提权风险。 - 规避
docker run -p 0.0.0.0裸奔:数据库等服务只监听内网或仅绑定 127.0.0.1。 - 镜像来源要可信:尽量用官方镜像或官方变体,慎用来路不明的第三方镜像。
# 只允许本机访问 mysql 端口(默认 -p 会绑到所有网卡) docker run -d -p 127.0.0.1:3306:3306 mysql:8.0
七、container 日志与排查流程
服务异常先看状态再逐层排查:
docker ps -a # 查看所有容器含已退出的 docker logs --tail 100 myapp # 最近 100 行日志 docker inspect myapp | grep -i exit # 查退出码 docker restart myapp
容器一直重启(Restarting 状态)通常是进程崩溃,配合 docker logs 找到报错;连接不上数据库多半是 compose 里服务名/网络地址写错,容器内用服务名而非 localhost 互相访问(compose 自动建隔离网络和 DNS)。
写在最后
Docker 把繁琐的"装环境、配依赖、防冲突"压缩成"拉镜像、写 Dockerfile、一键 up"。对个人 VPS 而言,它带来的是可复现的部署和干净的回滚体验。上手路径建议:先用 docker run 跑通单个服务 → 手写 Dockerfile 打好自己应用 → 再用 docker-compose 编排多服务。记住容器无状态、数据走卷、端口别裸奔这三条铁律,你就能安全地从零把 LNMP 或任意多服务架在 Docker 之上。


