在这里插入图片描述

👋 大家好,欢迎来到我的技术博客!
📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。
🎯 本文将围绕Docker这个话题展开,希望能为你带来一些启发或实用的参考。
🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获!


文章目录

Docker - 第一个Hello World容器运行实操 🐳✨

“Hello, world.” —— 这句简短却充满仪式感的问候,不仅是程序员踏入编程世界的第一个脚印,更是我们与容器化时代握手的第一声回响。当 System.out.println("Hello, World!"); 在 JVM 中轻盈跃出,而 docker run hello-world 在终端中迸发绿色字符时,两种范式在此刻交汇:代码的逻辑之美环境的确定之稳,正悄然重构现代软件交付的底层契约。

本文将带你从零开始,亲手启动你的第一个 Docker 容器——不止是 hello-world 镜像的“开箱即用”,更将深入构建一个可编译、可运行、可调试、可观察的 Java Hello World 应用容器。你将亲手编写 Java 代码、编写 Dockerfile、构建镜像、运行容器、查看日志、进入交互式 Shell,并延伸至多阶段构建优化、端口映射、环境变量注入等核心实践。全程无需虚拟机、不依赖特定云平台,仅需一台装有 Docker 的 Linux/macOS/Windows(WSL2)机器,即可完成一次完整、真实、可复现的容器初体验 🌟。


一、前置准备:安装与验证 Docker 环境 ✅

在开始编码之前,请确保 Docker 引擎已在本地正确安装并运行。

1.1 检查 Docker 是否就绪

打开终端(Terminal / PowerShell / WSL),执行:

docker --version

预期输出类似:

Docker version 24.0.7, build afdd53b

再运行经典健康检查:

docker info | head -n 10

若无报错(如 Cannot connect to the Docker daemon),说明守护进程(Docker Engine)正在后台运行 ✅。

💡 小贴士:

  • macOS 用户推荐使用 Docker Desktop for Mac(含 Kubernetes 支持)
  • Windows 用户请启用 WSL2 后端(非旧版 Hyper-V),官方文档详述配置步骤
  • Linux 用户(Ubuntu/Debian/CentOS)可直接通过包管理器安装,例如 Ubuntu:
    sudo apt update && sudo apt install docker.io
    sudo systemctl enable --now docker
    sudo usermod -aG docker $USER  # 免 sudo 运行 docker 命令(需重新登录终端)
    

1.2 运行首个容器:hello-world 验证链路 🌈

执行这行命令,它将自动拉取官方镜像、启动容器、打印欢迎信息、立即退出:

docker run hello-world

你将看到一段彩色文字,结尾类似:

Hello from Docker!
This message shows that your installation appears to be working correctly.
...

✅ 这意味着:

  • Docker 客户端能与守护进程通信
  • 镜像仓库(Docker Hub)网络可达
  • 容器运行时(containerd/runc)工作正常

🔗 扩展阅读:Docker 官方入门指南 → https://docs.docker.com/get-started/
这里不仅涵盖安装,还包含交互式教程(含浏览器内嵌终端),是极佳的补充学习资源。


二、动手写 Java:一个真正“可容器化”的 Hello World 🧾

现在,让我们告别“玩具镜像”,进入真实开发场景:用 Java 编写一个带 HTTP 接口的微服务,然后将其打包为容器镜像

2.1 创建项目结构

新建目录 java-hello-docker,并构建标准 Maven 目录结构:

mkdir -p java-hello-docker/src/main/java/com/example/hello
cd java-hello-docker

2.2 编写核心 Java 类:HelloWorldServlet.java

创建文件 src/main/java/com/example/hello/HelloWorldServlet.java

package com.example.hello;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

// ✨ 一个极简但完整的 Servlet:返回 HTML 格式的 Hello World + 当前时间
public class HelloWorldServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private static final DateTimeFormatter FORMATTER =
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {

        String name = req.getParameter("name");
        if (name == null || name.trim().isEmpty()) {
            name = "World";
        }

        String time = LocalDateTime.now().format(FORMATTER);
        String hostName = System.getenv("HOSTNAME"); // 容器 hostname,用于标识实例
        String appName = System.getProperty("app.name", "JavaHelloApp");

        resp.setContentType("text/html;charset=UTF-8");
        resp.setStatus(HttpServletResponse.SC_OK);

        StringBuilder html = new StringBuilder();
        html.append("<!DOCTYPE html>")
                .append("<html><head><title>Hello from Java</title>")
                .append("<style>body{font-family:system-ui, sans-serif; padding:4rem 2rem; text-align:center; background:#f8f9fa;}")
                .append("h1{color:#2c3e50;} .meta{color:#7f8c8d; font-size:0.9rem; margin-top:2rem;}</style>")
                .append("</head><body>")
                .append("<h1>🎉 Hello, ").append(name).append("!</h1>")
                .append("<p>This response is served by <strong>").append(appName).append("</strong></p>")
                .append("<p>Time: <strong>").append(time).append("</strong></p>")
                .append("<p>Running in container: <code>").append(hostName != null ? hostName : "unknown").append("</code></p>")
                .append("<div class='meta'>Built with Java 17 + Jetty • Dockerized ✅</div>")
                .append("</body></html>");

        resp.getWriter().write(html.toString());
    }
}

📌 关键设计说明

  • 使用 javax.servlet(非 Spring Boot),降低依赖复杂度,突出容器本质
  • 读取 HOSTNAME 环境变量 → 后续将在容器中验证其值(每个容器有唯一 hostname)
  • 支持 ?name=Alex 查询参数 → 展示请求动态性
  • 内嵌 CSS 实现轻量响应式页面 → 无需外部资源,纯静态 HTML 渲染

2.3 编写主启动类:HelloWorldServer.java

创建 src/main/java/com/example/hello/HelloWorldServer.java

package com.example.hello;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;

// ⚙️ 使用嵌入式 Jetty(轻量、无 Tomcat 依赖、启动快)
public class HelloWorldServer {
    public static void main(String[] args) throws Exception {
        int port = Integer.parseInt(System.getProperty("server.port", "8080"));

        Server server = new Server(port);
        ServletContextHandler context = new ServletContextHandler();
        context.setContextPath("/");
        server.setHandler(context);

        context.addServlet(new ServletHolder(new HelloWorldServlet()), "/hello");
        context.addServlet(new ServletHolder(new HelloWorldServlet()), "/"); // 根路径也响应

        System.out.printf("🚀 Java Hello World server starting on http://localhost:%d/\n", port);
        System.out.println("💡 Try: curl http://localhost:" + port + "/?name=Docker");
        System.out.println("💡 Or open browser → http://localhost:" + port + "/");

        server.start();
        server.join(); // 阻塞主线程,保持 JVM 运行
    }
}

📚 Jetty 是 Apache 顶级项目,以轻量、高性能、易嵌入著称。相比 Spring Boot 的 spring-boot-starter-web,它更贴近“裸容器”哲学——我们只容器化业务逻辑,而非框架全家桶
🔗 官方文档:https://www.eclipse.org/jetty/documentation/

2.4 编写 Maven 构建配置:pom.xml

在项目根目录创建 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>java-hello-docker</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <jetty.version>11.0.20</jetty.version>
    </properties>

    <dependencies>
        <!-- Jetty Server Core -->
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-server</artifactId>
            <version>${jetty.version}</version>
        </dependency>
        <!-- Jetty Servlet Support -->
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-servlet</artifactId>
            <version>${jetty.version}</version>
        </dependency>
        <!-- Jakarta Servlet API (required by Jetty 11+) -->
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <version>5.0.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- Shade plugin to create fat jar -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.4.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>com.example.hello.HelloWorldServer</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

✅ 此 pom.xml 实现:

  • JDK 17 编译兼容(Docker 官方 OpenJDK 17 镜像已成主流)
  • 使用 maven-shade-plugin 打包为 fat jar(含所有依赖),避免容器内 ClassNotFound
  • 显式声明 jakarta.servlet-api(Jetty 11+ 已迁移至 Jakarta EE 9+ 命名空间)

2.5 本地构建与运行(非容器)🧪

确保你已安装 JDK 17+ 和 Maven 3.8+:

java -version  # 应输出 openjdk version "17.x.x"
mvn -v         # 应输出 Apache Maven 3.8+

执行构建:

mvn clean package -DskipTests

成功后,生成 target/java-hello-docker-1.0.0.jar

运行它:

java -jar target/java-hello-docker-1.0.0.jar

终端将输出:

🚀 Java Hello World server starting on http://localhost:8080/
💡 Try: curl http://localhost:8080/?name=Docker
💡 Or open browser → http://localhost:8080/

✅ 打开浏览器访问 http://localhost:8080/?name=Docker,你将看到一个清爽的 HTML 页面,显示:

🎉 Hello, Docker!
This response is served by JavaHelloApp
Time: 2024-06-15 14:22:35.123
Running in container: unknown

🎯 注意最后一行:“Running in container: unknown” —— 因为当前仍在宿主机 JVM 中运行,HOSTNAME 环境变量非容器上下文。这个字段,将在我们进入 Docker 世界后,变成独一无二的容器 ID 或自定义名称。


三、编写 Dockerfile:定义容器蓝图 🗺️

Dockerfile 是容器的“施工图纸”。它声明了:基于哪个操作系统?安装什么依赖?复制哪些文件?以什么用户运行?监听什么端口?启动什么命令?

在项目根目录(与 pom.xml 同级)创建文件 Dockerfile

# 🐘 多阶段构建第一阶段:构建 Java 应用(Build Stage)
FROM maven:3.9.6-openjdk-17-slim AS builder

# 设置工作目录
WORKDIR /app

# 复制 pom.xml 先(利用 Docker layer cache 加速)
COPY pom.xml .

# 下载依赖(此层缓存率极高)
RUN mvn dependency:go-offline -B

# 复制源码
COPY src ./src

# 执行编译与打包(生成 fat jar)
RUN mvn clean package -DskipTests

# 🐳 第二阶段:运行时镜像(Runtime Stage)
FROM openjdk:17-jre-slim

# 创建非 root 用户增强安全性(最佳实践!)
RUN addgroup -g 1001 -f appgroup && adduser -S appuser -u 1001

# 设定工作目录
WORKDIR /app

# 从 builder 阶段复制 fat jar
COPY --from=builder /app/target/java-hello-docker-1.0.0.jar /app/app.jar

# 暴露端口(仅声明,不自动映射)
EXPOSE 8080

# 切换到非 root 用户
USER appuser

# 设置 JVM 参数(合理堆内存、禁用 JMX 等)
ENV JAVA_OPTS="-Xms64m -Xmx128m -Djava.security.egd=file:/dev/./urandom"

# 启动命令:java -jar + 环境变量覆盖端口
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -Dserver.port=$PORT -jar /app/app.jar"]

🔍 Dockerfile 关键特性解析:

行号 指令 说明 ✨ 为什么重要
1 FROM maven:... AS builder 使用官方 Maven 镜像作为构建阶段基础 避免在最终镜像中携带 mvngitgcc 等构建工具,减小攻击面
13 FROM openjdk:17-jre-slim 生产镜像仅含 JRE(非 JDK),体积更小(≈ 120MB vs 450MB) 减少漏洞数量、加快拉取、提升启动速度
17 adduser -S appuser 创建系统用户并切换 遵循 CIS Docker Benchmark 安全基线:禁止 root 运行容器进程
23 EXPOSE 8080 文档化容器监听端口 docker ps 可见,且是 docker run -P 自动映射的依据
27 ENTRYPOINT [...] 使用 shell 形式支持环境变量插值($PORT 允许运行时灵活指定端口,如 docker run -e PORT=9090 ...

📌 多阶段构建(Multi-stage Build)是现代 Docker 最佳实践之一:它将“构建环境”与“运行环境”彻底隔离,最终镜像不含任何编译器、测试框架或源码,仅保留最小运行时依赖。


四、构建并运行容器:见证 Hello World 的容器化诞生 🌟

4.1 构建镜像

java-hello-docker/ 目录下执行:

docker build -t java-hello-app .

你会看到逐层构建日志,最后出现:

 => exporting to image                                                                                                         0.1s
 => => exporting layers                                                                                                        0.0s
 => => writing image sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx                                   0.0s
 => => naming to docker.io/library/java-hello-app                                                                              0.0s

✅ 镜像已构建完成。查看它:

docker images | grep java-hello-app

输出类似:

java-hello-app   latest    xxxxxxxx   47 seconds ago   128MB

💡 镜像大小仅 128MB —— 对比传统虚拟机(GB 级)、甚至某些臃肿的 Spring Boot 镜像(300MB+),这是精简构建带来的真实收益。

4.2 运行容器(基础模式)

docker run -p 8080:8080 java-hello-app
  • -p 8080:8080:将宿主机 8080 端口映射到容器 8080 端口
  • 终端将打印 Jetty 启动日志,最后停在 Started Server@xxxxxx

✅ 现在访问 http://localhost:8080/?name=Docker,你将看到完全相同的页面,但注意最后一行变化:

Running in container: 3a7b9c1d2e4f

👉 这个 3a7b9c1d2e4f 就是 Docker 分配给该容器的 随机 hostname(即容器 ID 前缀),证明你已真正在容器中运行!

4.3 运行容器(生产增强模式)⚡

添加更多实用参数,模拟真实部署场景:

docker run -d \
  --name my-java-hello \
  -p 8080:8080 \
  -e PORT=8080 \
  -e app.name="My Dockerized Java App 🐋" \
  -v $(pwd)/logs:/app/logs \
  --restart unless-stopped \
  --memory=256m \
  --cpus=0.5 \
  java-hello-app

参数详解:

  • -d:后台守护进程模式(detached)
  • --name my-java-hello:赋予容器可读名称,便于管理
  • -e PORT=8080:覆盖默认端口(虽此处同值,但体现可配置性)
  • -e app.name="...":注入自定义应用名,HTML 页面将显示该值
  • -v $(pwd)/logs:/app/logs:挂载宿主机 logs/ 目录到容器 /app/logs(本例未写日志,但为扩展预留)
  • --restart unless-stopped:容器异常退出后自动重启(除手动 docker stop 外)
  • --memory=256m --cpus=0.5:资源限制,防止单个容器耗尽主机资源

✅ 查看运行中容器:

docker ps -f name=my-java-hello

输出:

CONTAINER ID   IMAGE           COMMAND                  CREATED         STATUS         PORTS                    NAMES
a1b2c3d4e5f6   java-hello-app  "sh -c 'java $JAVA_…"   3 seconds ago   Up 2 seconds   0.0.0.0:8080->8080/tcp   my-java-hello

✅ 查看实时日志:

docker logs -f my-java-hello

你将看到每笔 HTTP 请求的 Jetty 访问日志(如 172.17.0.1 - - [15/Jun/2024:14:30:22 +0000] "GET /?name=Docker HTTP/1.1" 200 523)。


五、深入容器内部:交互式调试与诊断 🔍

容器不是黑盒。Docker 提供强大工具助你“登船检查”。

5.1 进入容器 Bash(需镜像含 sh

我们的 openjdk:17-jre-slim 镜像自带 sh,执行:

docker exec -it my-java-hello sh

你将进入容器内部 Shell,路径为 /app

/app # ls -l
total 12
-rw-r--r--    1 appuser  appgroup    11234656 Jun 15 14:25 app.jar

/app # echo $HOSTNAME
a1b2c3d4e5f6

/app # cat /proc/1/cmdline | tr '\0' '\n'
sh
-c
java $JAVA_OPTS -Dserver.port=$PORT -jar /app/app.jar

✅ 验证了:

  • HOSTNAME 环境变量正是容器 ID
  • PID 1 进程即为 Java 启动命令
  • app.jar 存在于 /app/ 目录

输入 exit 退出。

5.2 查看容器详细信息(Inspect)

docker inspect my-java-hello

输出为 JSON,包含 IP 地址、挂载点、网络配置、状态、启动命令等全部元数据。重点关注:

"NetworkSettings": {
  "IPAddress": "172.17.0.2",
  "Ports": { "8080/tcp": [{ "HostIp": "0.0.0.0", "HostPort": "8080" }] }
},
"Config": {
  "Env": ["PORT=8080", "app.name=My Dockerized Java App 🐋"],
  "Cmd": null,
  "Entrypoint": ["sh", "-c", "java $JAVA_OPTS -Dserver.port=$PORT -jar /app/app.jar"]
}

✅ 这就是容器的“数字身份证”,是自动化运维、Kubernetes 调度的基础数据源。


六、可视化理解:Docker 核心组件协作流程 🧩

Docker 不是单体软件,而是由多个松耦合组件协同工作的平台。以下 Mermaid 图表清晰展示一次 docker run 命令背后发生了什么:

Host OS

1. docker build

2. API Request

3. Pull Image

4. Create Container

5. Start Container

开发者

Docker CLI

Docker Daemon
(dockerd)

Containerd
(容器运行时管理)

runc
(OCI 兼容运行时)

Linux Kernel
Namespaces & Cgroups

Docker Hub
(镜像仓库)

📌 图解说明

  • Docker CLI(蓝色):用户入口,发送 REST API 请求
  • Docker Daemon(橙色):核心守护进程,接收 CLI 请求,协调全局资源
  • Containerd(紫色):符合 OCI(Open Container Initiative)标准的工业级容器管理器,负责镜像分发、容器生命周期(create/start/stop)
  • runc(青色):轻量级 CLI 工具,直接调用 Linux kernel 特性(namespaces 隔离进程视图,cgroups 限制资源)
  • Docker Hub(灰色):公共镜像仓库,docker pull 默认源
  • Linux Kernel(红色):一切容器化的根基 —— 没有 namespace/cgroup,就没有容器 🌐

✅ 正因这种分层架构,Docker 可被其他运行时(如 Podman、CRI-O)替代,而上层工具(如 Kubernetes)仍可无缝工作 —— 这就是 标准化的力量


七、进阶实战:同一镜像,多环境部署 🌐

Docker 的核心价值之一:Build Once, Run Anywhere。我们用同一镜像,在不同场景下运行:

7.1 场景一:本地开发调试(已演示)

docker run -p 8080:8080 -e app.name="Dev Env 🛠️" java-hello-app

7.2 场景二:测试环境(绑定固定端口 + 日志卷)

docker run -d \
  --name hello-test \
  -p 8081:8080 \
  -e PORT=8080 \
  -v $(pwd)/test-logs:/app/logs \
  java-hello-app

7.3 场景三:CI/CD 流水线中的健康检查(Health Check)

修改 Dockerfile,在末尾添加:

# 添加健康检查:每10秒调用一次 /hello 接口,3次失败则标记 unhealthy
HEALTHCHECK --interval=10s --timeout=3s --start-period=30s --retries=3 \
  CMD curl -f http://localhost:8080/hello || exit 1

重建镜像后,运行:

docker run -d --name hello-health -p 8082:8080 java-hello-app
docker ps --filter name=hello-health --format "table {{.ID}}\t{{.Status}}\t{{.Names}}"

你会看到状态列含 healthy 字样 ✅。健康检查是 Kubernetes/Liveness Probe 的前提。

7.4 场景四:多容器协作(Docker Compose 简介)

创建 docker-compose.yml(同级目录):

version: '3.8'
services:
  web:
    image: java-hello-app
    ports:
      - "8080:8080"
    environment:
      - app.name=Hello World Web 🌐
      - PORT=8080
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/hello"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 40s

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - web

配套 nginx.conf(反向代理):

events { worker_connections 1024; }
http {
  upstream backend { server web:8080; }
  server {
    listen 80;
    location / {
      proxy_pass http://backend;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
    }
  }
}

启动整套服务:

docker compose up -d

✅ 访问 http://localhost/,流量经 Nginx 代理至 Java 容器 —— 这就是微服务网关的雏形。

🔗 Docker Compose 官方文档(含大量生产案例)→ https://docs.docker.com/compose/


八、常见问题排障指南 ❓

遇到问题?别慌,以下是高频场景与解决方案:

现象 可能原因 解决方案
docker: command not found Docker CLI 未安装或未加入 PATH 重新安装 Docker Desktop / 检查 which docker
Cannot connect to the Docker daemon Docker Engine 未运行 macOS/Windows:打开 Docker Desktop;Linux:sudo systemctl start docker
docker run hello-world 报 TLS 错误 Docker 客户端与守护进程版本不匹配 升级 Docker Desktop 或重置 daemon 配置
容器启动后立即退出(STATUS Exited (1) ENTRYPOINTCMD 命令执行失败/退出 docker logs <container> 查看错误;docker run -it <image> sh 进入调试
访问 http://localhost:8080 超时 端口未正确映射或应用未监听 0.0.0.0 检查 docker ps 的 PORTS 列;确认 Java 代码中 Jetty 绑定的是 0.0.0.0:8080(默认即是)
ClassNotFoundException fat jar 未正确构建或路径错误 进入容器 sh,执行 java -jar /app/app.jar 手动测试;检查 Dockerfile COPY 路径
Permission denied 写日志 容器用户 appuser 对挂载目录无写权限 宿主机创建目录后 chmod 777 logs/(开发临时方案)或 chown 1001:1001 logs/(生产推荐)

💡 黄金排障三步法

  1. docker logs <container> → 查看应用 stdout/stderr
  2. docker inspect <container> → 查看配置、网络、状态
  3. docker exec -it <container> sh → 登录容器,手动执行命令验证

九、安全与生产就绪建议 🔐

容器 ≠ 免疫病毒。生产环境需额外加固:

9.1 镜像扫描(免费方案)

使用 Docker 官方 docker scan(基于 Snyk):

docker scan java-hello-app

它将检测基础镜像(openjdk:17-jre-slim)中的已知 CVE 漏洞,并给出修复建议(如升级到更新的 patch 版本)。

9.2 最小权限原则(已实践)

  • ✅ 使用非 root 用户(USER appuser
  • ✅ 使用 jre-slim 而非 jdkfull 镜像
  • ✅ 多阶段构建移除构建工具

9.3 禁用不必要能力

运行时显式放弃危险 Linux capability:

docker run --cap-drop=ALL --read-only --tmpfs /tmp java-hello-app
  • --cap-drop=ALL:丢弃所有 Linux capabilities(如 CAP_NET_ADMIN
  • --read-only:根文件系统只读(需确保应用日志写入 /tmp 或挂载卷)
  • --tmpfs /tmp:提供可写临时目录

9.4 使用 .dockerignore(防意外泄露)

在项目根目录创建 .dockerignore

.git
.gitignore
target/
README.md
Dockerfile
pom.xml
src/

防止 COPY . . 时把 target/(含旧 jar)、.git 等无用目录打入镜像,加速构建并减小体积。


十、总结:Hello World 之后,你已掌握的容器核心能力 🎯

回顾整个实操旅程,你已亲手完成了:

能力维度 具体实践 ✅ 已掌握
环境一致性 同一 Dockerfile,在 macOS/Linux/Windows 上构建相同镜像 ✔️
依赖隔离 Java 应用与宿主机 JDK 版本解耦,镜像内含完整 JRE ✔️
进程封装 java -jar 进程被 namespace 隔离,拥有独立 PID/Network/UTS 空间 ✔️
资源可控 通过 --memory, --cpus 限制容器资源使用 ✔️
配置外置 通过 -e 注入环境变量,实现 app.namePORT 动态配置 ✔️
持久化扩展 通过 -v 挂载日志目录,实现容器内/外数据共享 ✔️
生命周期管理 docker run/start/stop/rm/logs/exec 全链路操作 ✔️
安全基线 非 root 用户、最小基础镜像、多阶段构建 ✔️
可观测性 健康检查、标准日志输出、inspect 元数据查询 ✔️

🌈 这不再是教科书上的概念,而是你指尖敲出的每一行命令、浏览器中刷新出的每一个 Hello, Docker!。容器技术从未如此触手可及。


十一、下一步学习路径 🚀

你的容器之旅才刚刚启航。推荐沿着以下方向深化:

  • 编排进阶:学习 Docker Swarm(原生集群)或 Kubernetes(行业标准),管理成百上千容器
  • CI/CD 集成:将 docker build 嵌入 Jenkins/GitLab CI,实现提交即构建、构建即部署
  • 镜像仓库私有化:搭建 Harbor,实现镜像签名、漏洞扫描、权限管控
  • 服务网格:引入 Istio,为 Java 微服务增加熔断、限流、分布式追踪能力
  • 无服务器容器:探索 AWS FargateGoogle Cloud Run,按需付费、免运维容器运行

🌟 最后,送你一句来自 Docker 官方的箴言:
“Containers are a standard unit of software that packages up code and all its dependencies so the application runs quickly and reliably from one computing environment to another.”
—— 容器,是代码与其所有依赖打包而成的标准软件单元,让应用得以快速、可靠地在任意计算环境中运行。

现在,关掉这篇博客,打开你的终端,输入:

docker run --rm -it alpine sh -c "echo 'Hello, World! 🐳✨' && echo 'You are now a Docker practitioner.'"

你,已经是一名 Docker 实践者了。恭喜!🎉
愿每一次 docker run,都成为你交付确定性的庄严仪式。


🙌 感谢你读到这里!
🔍 技术之路没有捷径,但每一次阅读、思考和实践,都在悄悄拉近你与目标的距离。
💡 如果本文对你有帮助,不妨 👍 点赞、📌 收藏、📤 分享 给更多需要的朋友!
💬 欢迎在评论区留下你的想法、疑问或建议,我会一一回复,我们一起交流、共同成长 🌿
🔔 关注我,不错过下一篇干货!我们下期再见!✨

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐