先别急着装Docker,咱们先想清楚一个事儿:你到底需要什么?如果是本地开发测试,装个Docker Desktop就够了;要是服务器部署,得用Docker Engine。我第一次就在服务器上装了桌面版,结果CPU占用直接飙到80%。
环境搭建避坑指南
“bash
Ubuntu 20.04+ 正确安装方式(别用apt直接装,版本太老)
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
验证安装(注意:这里必须重启终端才能生效)
docker --versio
n # Docker version 24.0.6
让你当前用户免sudo执行(否则每次都要输入密码,烦死)
sudo usermod -aG docker $USER
newgrp docker # 立即生效,不用登出
`
为什么要这么写?因为官方仓库的版本经常落后两三个大版本,我之前用apt装的还是19.x,连docker compose v2都不支持。还有那个免sudo配置,不设置的话你写脚本时会疯狂报"permission denied"。
(开篇配图:一个开发者在终端前输入docker命令,旁边显示"permission denied"的报错截图,配文"第一天踩坑实录")
第一个实战:Spring Boot应用容器化
先看一个最常见的场景——部署Java微服务。很多人上来就写Dockerfile,结果镜像体积赶上操作系统了。
`dockerfile
这才是正确的多阶段构建写法
第一阶段:编译
FROM maven:3.8.4-openjdk-11-slim AS build
WORKDIR /app
COPY pom.xml .
先下载依赖(利用Docker缓存层)
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTe
sts
第二阶段:运行(只保留运行时的东西)
FROM openjdk:11-jre-slim
WORKDIR /app
注意:这里直接从build阶段复制,不保留maven工具
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
`
这个设计真的反人类——很多人把编译和运行放在同一个镜像里,结果一个Spring Boot应用搞出1.2GB。上面的写法把镜像分成两个阶段,最终只保留JRE和jar包,体积直接降到180MB。
部署命令:
`bash
构建镜像(-t 是打标签,别用默认的latest,容易混淆)
docker build -t myapp:1.0.0 .
运行容器(-d后台,-p端口映射,--name命名)
docker run -d -p 8080:8080 --name myapp-prod myapp:1.0.0
查看日志(实时监控用-f)
docker logs -f myapp-prod
`
另一个坑:端口映射。我第一次部署时忘了映射端口,应用跑起来了但访问不到,排查了半小时才发现。还有个技巧:用docker run –rm临时跑测试容器,退出自动删除,省得清理垃圾。
第二个实战:容器间通信与数据持久化
现在应用跑起来了,但你的应用需要连数据库吧?别再用localhost了,那是容器内部的localhost,不是你宿主机的。
`yaml
docker-compose.yml 这才是正确的多服务部署
version: '3.8'
services:
mysql:
image: mysql:8.0
# 环境变量不要写在Dockerfile里,用运行时的配置
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
MYSQL_DATABASE: myapp
# 数据持久化!否则容器删了数据全没了
volumes:
- mysql_data:/var/lib/mysql
networks:
- app_network
# 健康检查,避免应用启动时数据库还没准备好
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 3
app:
build: .
ports:
- "8080:8080"
environment:
# 用服务名通信,不是IP地址
DB_HOST: mysql
DB_PORT: 3306
depends_on:
mysql:
condition: service_healthy
volumes:
# 日志挂载出来方便排查
- app_logs:/app/logs
networks:
- app_network
volumes:
mysql_data:
app_logs:
networks:
app_network:
driver: bridge
`
为什么要这么写?因为容器重启后IP会变,用服务名通信才靠谱。还有那个healthcheck,我第一次部署时应用启动报"连接数据库超时",就是因为数据库还没完全启动好,应用就急着去连。
(核心配图:一个可视化网络拓扑图,显示app容器和mysql容器通过bridge网络连接,旁边用箭头标注"服务名通信"和"volume数据持久化")
还有个技巧:用.env文件管理环境变量,别把密码硬编码在compose文件里。我见过最骚的操作是把数据库密码写在Dockerfile里,然后不小心push到GitHub,第二天数据库就被黑了。
生产级优化:镜像瘦身与安全加固
这是压箱底的经验。生产环境部署不是跑起来就行,要考虑镜像大小、安全漏洞、日志管理。
`dockerfile
终极瘦身版Dockerfile
FROM alpine:3.18 AS base
用alpine基础镜像,只有5MB
RUN apk add --no-cache openjdk11-jre
FROM base AS final
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
非root用户运行(安全第一)
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD wget -qO- http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", "-jar", "app.jar"]
`
这个设计真的反人类——官方文档写用户权限那段跟谜语一样,我查了三个小时才搞明白怎么创建非root用户。但这一步必须做,否则容器被攻破后攻击者直接获得root权限。
性能数据对比:
- 原始镜像:1.2GB -> 启动时间 45秒
- 多阶段构建:180MB -> 启动时间 12秒
- 最终优化版:85MB -> 启动时间 6秒
从 45 秒降到 6 秒,这就是容器化的威力。
日常运维命令大全
这些命令是我踩坑无数后整理的,建议收藏:
`bash
查看容器资源占用(CPU、内存、网络IO)
docker stats
进入容器内部排查(别用attach,用exec)
docker exec -it container_name sh
清理所有无用资源(磁盘不够时用)
docker system prune -af --volumes
查看日志并过滤关键词
docker logs --tail 100 -f myapp | grep "ERROR"
保存和加载镜像(离线部署用)
docker save myapp:1.0.0 | gzip > myapp.tar.gz
gunzip -c myapp.tar.gz | docker load
`
还有个技巧:用docker events`实时监控容器事件,配合webhook可以实现自动报警。
(总结前配图:一个Docker命令行终端的截图,显示docker stats的实时输出,包含CPU、内存、网络IO数据,配文”生产环境巡检利器”)
总结一下,你可以立刻用的三个点
记住,Docker不是完全隔离的虚拟化,它共享宿主机内核。所以别在容器里跑不信任的应用,也别把生产数据放在容器内部。
最后提醒一句:刚开始不要追求完美,先跑起来,再优化。我就是一开始想着一步到位,结果光选基础镜像就纠结了三天。