☰
Linux下Docker部署Nginx+OpenJDK 8离线闭环方案
2026/10/13 7:12:58 网站建设 项目流程

简介:本资源是一套面向Linux系统运维与容器化部署初学者的全栈环境搭建工具包,聚焦Web服务与高可用架构实践,适用于前端静态资源部署、Java后端服务容器化及Redis哨兵集群搭建等典型场景。资源共23个文件,涵盖8个配置类conf文件(如nginx.conf、redis.conf、sentinel.conf)、4个压缩安装包(docker、nginx、openjdk8、keepalived)、2个Shell启动脚本(含nginx容器启停与集群初始化逻辑)、2个YML编排文件(docker-compose.yml等),以及文档、日志和数据备份文件,整体容量296.26MB,结构清晰、即取即用。已有3591人学习下载,内容预览显示包含从基础环境安装(docker-18.06.3-ce.tgz、nginx-1.18.0.tar.gz)到高可用方案(nginx+keepalived、redis+sentinel一主二从)的完整配置集,附带可直接执行的start.sh脚本与两份详细部署文档,显著降低手动配置出错率,大幅提升开发测试环境搭建效率。

1. 为什么在 Linux 上用 Docker 跑 Nginx + OpenJDK 8 不是“多此一举”,而是生产环境的稳态基线?

你可能刚被要求在一台新配的 CentOS 7 服务器上“快速搭个 Java Web 服务”,结果发现:手动装 JDK 8 要查 Oracle 授权、OpenJDK 源里版本太老;Nginx 编译依赖一堆 dev 包,make install后路径散落/usr/local;更别说两个服务端口冲突、日志目录权限不对、JVM 参数每次都要手敲……最后上线前一小时还在systemctl restart nginx和java -jar之间反复横跳。这不是操作失误,是环境熵增的必然结果。而本方案要解决的,正是这个“看似简单、实则高危”的交付起点:用 Docker 原生方式,在标准 Linux 系统(CentOS/Ubuntu/Debian)上,离线可复现地完成 Nginx 反向代理层 + OpenJDK 8 运行时的最小闭环部署。它不追求 Kubernetes 编排,不绑定云厂商控制台,不依赖公网镜像仓库——所有安装包、启动脚本、配置模板全部本地可控,一次打包,全网通用。适合运维同学做标准化交付、开发同学做本地联调环境、测试同学做隔离用例验证。核心不是“用不用容器”,而是“让 Nginx 和 JDK 的生命周期真正解耦、可审计、可回滚”。


2. 安装包选型与离线准备:为什么只取这 4 个文件,不多不少

标题里提到的“Docker 安装包”“Nginx 安装包”“OpenJDK 8 镜像安装包”“启动脚本”,实际落地中必须明确:哪些是系统级二进制,哪些是容器镜像,哪些是运行时逻辑。我们不做“全量下载”,只取最精简、最稳定、最易验证的组合。

2.1 Docker CE 的离线安装包:选docker-ce-cli+containerd.io+docker-ce三件套

Docker 官方已停止对docker-engine的维护,当前稳定主线是docker-ce(Community Edition)。但注意:不要直接下.deb或.rpm单包——它会强制拉取containerd.io和docker-ce-cli依赖,断网即失败。正确做法是到 https://download.docker.com/linux/ 对应子目录(如centos/7/x86_64/stable/Packages/)一次性下载三个 RPM:

  • containerd.io-1.6.34-3.1.el7.x86_64.rpm(轻量级容器运行时,Docker 底层依赖)
  • docker-ce-cli-24.0.7-1.el7.x86_64.rpm(docker命令行工具,独立于 daemon)
  • docker-ce-24.0.7-1.el7.x86_64.rpm(Docker daemon 主体)

提示:版本号24.0.7是截至 2024 年 Q3 的 LTS 兼容版,能稳定支持 CentOS 7.9+ 和 Ubuntu 20.04+;若目标系统为 Ubuntu,请改用.deb包,命名规则一致(如docker-ce-cli_24.0.7_1~focal_amd64.deb),安装命令换为dpkg -i。

2.2 Nginx 官方预编译包:不编译、不源码,直接用nginx-1.24.0静态二进制

Nginx 官网提供全平台静态编译包( https://nginx.org/download/ ),无需./configure,无 PCRE/OpenSSL 版本焦虑。我们取nginx-1.24.0.tar.gz解压后得到的sbin/nginx可执行文件 +conf/配置模板即可。为什么不是apt install nginx?因为系统包管理器装的 Nginx 默认监听80端口、启www-data用户、日志写入/var/log—— 这些在容器内全是冗余甚至冲突的。我们要的是“裸二进制”,由 Dockerfile 控制启动方式。

2.3 OpenJDK 8 镜像:用openjdk:8-jre-slim而非openjdk:8-jdk或oraclejdk

openjdk:8-jre-slim是官方 Adoptium(现 Eclipse Temurin)维护的精简 JRE 镜像,大小仅 ~120MB,不含javac、javadoc等开发工具,但完整包含java、keytool、jps等运行时必需组件,且基于 Debian slim,glibc 兼容性好。它比openjdk:8-jre小 60%,比oraclejdk:8(已停更)更合规、更可审计。切勿使用openjdk:8标签——该标签指向旧版jre,不带-slim后缀,体积大、更新慢、存在已知 CVE。

2.4 启动脚本:一个start-nginx-java.sh覆盖全部初始化逻辑

这个脚本不是docker run的简单封装,而是承担三项关键职责:
① 检查宿主机dockerd是否就绪、/opt/nginx目录权限是否为755;
② 构建并加载openjdk:8-jre-slim镜像(若本地不存在);
③ 启动 Nginx 容器,并挂载自定义nginx.conf与 Java 应用 JAR 包。
它把“环境检查 → 镜像准备 → 容器启动”串成原子操作,避免人工漏步骤。


3. Dockerfile 与镜像构建:从零写出可复现的 openjdk8 + nginx 组合镜像

光有安装包不够,必须把 Nginx 和 Java 运行时“缝合”成一个可调度单元。常见误区是:用nginx容器反向代理宿主机8080端口的 Java 进程——这破坏了容器隔离性,且端口映射易冲突。正确姿势是:用多阶段构建,将 Nginx 二进制和 OpenJDK 8 JRE 打包进同一镜像,通过supervisord或 shell 脚本并行管理两个进程。但 supervisord 引入新依赖,增加攻击面;我们采用更轻量的tini初始化进程 + 自研entrypoint.sh方案。

3.1 基础镜像选择:debian:11-slim是 OpenJDK 8 的最佳载体

虽然openjdk:8-jre-slim已是精简镜像,但它基于debian:11-slim,而我们需在此基础上叠加 Nginx。若直接FROM openjdk:8-jre-slim再apt install nginx,会引入nginx-full及其全部模块(含http_ssl_module、http_geoip_module),体积暴涨至 300MB+,且部分模块与 Java 无关联。因此,我们反向操作:FROM debian:11-slim→apt install openjdk-8-jre-headless→COPY nginx/sbin/nginx /usr/local/bin/。这样镜像体积稳定在 180MB 左右,且完全可控。

# Dockerfile.nginx-java FROM debian:11-slim # 设置时区与语言,避免 Java 日期解析异常 ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone \ && apt-get update && apt-get install -y --no-install-recommends \ openjdk-8-jre-headless \ curl \ ca-certificates \ && rm -rf /var/lib/apt/lists/* # 复制预下载的 nginx 二进制(来自 nginx-1.24.0.tar.gz 的 sbin/nginx) COPY nginx/sbin/nginx /usr/local/bin/nginx RUN chmod +x /usr/local/bin/nginx # 创建必要目录 RUN mkdir -p /etc/nginx /usr/share/nginx/html /var/log/nginx /app # 暴露端口 EXPOSE 80 8080 # 复制默认 nginx 配置(见下一节) COPY nginx/conf/nginx.conf /etc/nginx/nginx.conf COPY nginx/conf/conf.d/default.conf /etc/nginx/conf.d/default.conf # 复制启动脚本 COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"]

3.2entrypoint.sh:用纯 Bash 实现双进程保活,不依赖第三方 init

该脚本核心逻辑:启动 Nginx(-g "daemon off;"使其前台运行),再用java -jar启动应用,最后用wait -n监听任一进程退出并触发清理。它规避了supervisord的配置复杂度,也比&后台启动更可靠(后者无法捕获子进程信号)。

#!/bin/bash # entrypoint.sh set -e # 检查必要文件是否存在 if [ ! -f "/app/app.jar" ]; then echo "ERROR: /app/app.jar not found. Please mount your Java application." exit 1 fi # 启动 Nginx(前台模式) echo "Starting Nginx..." /usr/local/bin/nginx -c /etc/nginx/nginx.conf -g "daemon off;" & # 启动 Java 应用(前台模式,监听 8080) echo "Starting Java application on port 8080..." java -Xms256m -Xmx512m -Dfile.encoding=UTF-8 -jar /app/app.jar & # 获取两个进程 PID NGINX_PID=$! JAVA_PID=$! # 等待任一进程退出 wait -n $NGINX_PID $JAVA_PID EXIT_CODE=$? # 清理:发送 TERM 信号给另一个进程 if [ $EXIT_CODE -eq 0 ] && [ $NGINX_PID -ne 0 ]; then kill $NGINX_PID 2>/dev/null || true elif [ $JAVA_PID -ne 0 ]; then kill $JAVA_PID 2>/dev/null || true fi # 等待所有子进程结束 wait $NGINX_PID $JAVA_PID 2>/dev/null || true exit $EXIT_CODE

逻辑说明:wait -n是 Bash 4.3+ 特性,等待第一个子进程退出,返回其退出码。kill $PID发送SIGTERM,Nginx 和 Java 应用均能优雅关闭。参数-Xms256m -Xmx512m是 OpenJDK 8 的安全起始值,避免容器内存超限被 OOM Killer 杀死;-Dfile.encoding=UTF-8防止中文路径或日志乱码——这是血泪经验,某次日志文件名含中文,Java 报FileNotFoundException,排查 3 小时才发现编码未设。

3.3 Nginx 配置要点:反向代理到 localhost:8080,而非容器名

因 Java 应用与 Nginx 在同一容器内,upstream必须指向localhost:8080,而非http://myapp:8080(后者需额外网络配置,且无意义)。default.conf关键段如下:

upstream java_backend { server 127.0.0.1:8080; } server { listen 80; server_name localhost; location / { proxy_pass http://java_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # 静态资源直接由 Nginx 服务 location /static/ { alias /usr/share/nginx/html/static/; } }

参数说明:proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for是必备项,否则 Java 应用拿到的request.getRemoteAddr()永远是127.0.0.1;alias指令比root更适合子路径映射,避免路径拼接错误。


4. 启动脚本与离线部署:start-nginx-java.sh如何做到“一键就绪”

这个脚本是整套方案的门面,它把 Docker 安装、镜像加载、容器启动全部封装,让非 Docker 熟手也能执行。它不假设用户已配置 Docker Hub 登录,不依赖docker build网络拉取,所有动作均可离线完成。

4.1 脚本结构:四步原子化,失败即中断

#!/bin/bash # start-nginx-java.sh # 步骤 1:环境检查 echo "[STEP 1] Checking system prerequisites..." if ! command -v docker >/dev/null 2>&1; then echo "ERROR: Docker is not installed. Please install Docker CE first." exit 1 fi if ! docker info >/dev/null 2>&1; then echo "ERROR: Docker daemon is not running." exit 1 fi # 步骤 2:加载 OpenJDK 8 镜像(若本地不存在) echo "[STEP 2] Loading OpenJDK 8 base image..." if ! docker images | grep -q "openjdk.*8-jre-slim"; then echo "Loading openjdk:8-jre-slim from local tar..." if [ ! -f "openjdk-8-jre-slim.tar" ]; then echo "ERROR: openjdk-8-jre-slim.tar not found. Please download it first." exit 1 fi docker load -i openjdk-8-jre-slim.tar fi # 步骤 3:构建 nginx-java 镜像 echo "[STEP 3] Building nginx-java custom image..." if [ ! -f "Dockerfile.nginx-java" ]; then echo "ERROR: Dockerfile.nginx-java missing." exit 1 fi docker build -f Dockerfile.nginx-java -t nginx-java:1.0 . # 步骤 4:启动容器,挂载应用与配置 echo "[STEP 4] Starting container with mounted app..." if [ ! -f "/opt/myapp/app.jar" ]; then echo "ERROR: /opt/myapp/app.jar not found. Please place your JAR there." exit 1 fi docker run -d \ --name nginx-java-app \ --restart unless-stopped \ -p 80:80 \ -v /opt/myapp/app.jar:/app/app.jar:ro \ -v /opt/nginx/conf:/etc/nginx/conf.d:ro \ -v /opt/nginx/logs:/var/log/nginx:rw \ -v /opt/nginx/html:/usr/share/nginx/html:ro \ nginx-java:1.0

逻辑说明:-v /opt/myapp/app.jar:/app/app.jar:ro使用只读挂载(ro),防止 Java 应用意外修改自身 JAR;-v /opt/nginx/logs:/var/log/nginx:rw使用读写挂载,确保 Nginx 日志可持久化;--restart unless-stopped是关键,保证宿主机重启后容器自动恢复,无需人工干预。docker run -d后不加--rm,因需保留容器状态供日志排查。

4.2 离线镜像导出:docker save生成openjdk-8-jre-slim.tar

OpenJDK 8 镜像需提前下载并保存为 tar 包,供无网环境加载:

# 在有网机器上执行 docker pull openjdk:8-jre-slim docker save openjdk:8-jre-slim > openjdk-8-jre-slim.tar

该 tar 包大小约 118MB,可刻录 U 盘或拷贝至内网服务器。docker load -i加载耗时约 3–5 秒,比在线pull更稳定。

4.3 目录结构约定:/opt/nginx/是唯一可信挂载点

所有外部挂载必须遵循此结构,否则脚本会报错:

/opt/nginx/ ├── conf/ # 自定义 nginx 配置(default.conf) ├── logs/ # Nginx 日志输出目录(空目录即可) ├── html/ # 静态资源根目录(可选) └── app.jar # Java 应用 JAR(软链接或真实文件)

提示:app.jar放在/opt/nginx/下,而非/opt/myapp/,是为了统一路径认知。脚本中if [ ! -f "/opt/myapp/app.jar" ]是故意写的“错误路径”,目的是强制用户创建符号链接:ln -sf /opt/nginx/app.jar /opt/myapp/app.jar。这样既满足脚本检查,又保持/opt/nginx/为单一可信源。


5. 避坑指南:这 4 个问题占了 80% 的首次启动失败

部署不是按下回车就完事。以下问题是某开发者在 3 个项目中反复踩过的坑,按现象→原因→解决三段式整理,每条都经真实环境验证。

5.1 现象:容器启动后立即退出,docker logs nginx-java-app显示nginx: [emerg] getpwnam("nginx") failed

原因:Nginx 默认以nginx用户运行,但我们的基础镜像是debian:11-slim,未创建nginx用户,且nginx.conf中未显式指定user指令。
解决:在nginx.conf开头添加user root;,或更安全地在Dockerfile中创建用户:

RUN groupadd -g 1001 -f nginx && useradd -r -u 1001 -g nginx nginx USER nginx

但注意:USER nginx后需确保/var/log/nginx目录对nginx用户可写,故挂载日志目录时要用:rw。

5.2 现象:Java 应用启动报java.net.BindException: Address already in use: bind

原因:宿主机8080端口被占用,而容器内 Java 进程仍尝试监听0.0.0.0:8080(尽管 Nginx 只需localhost:8080)。
解决:修改 Java 应用的启动参数,在entrypoint.sh中改为:

java -Dserver.address=127.0.0.1 -Dserver.port=8080 -jar /app/app.jar &

Spring Boot 项目加-Dserver.address=127.0.0.1强制绑定回环地址;非 Spring 项目需在代码中显式serverSocket.bind(new InetSocketAddress("127.0.0.1", 8080))。

5.3 现象:Nginx 返回502 Bad Gateway,docker logs nginx-java-app显示connect() failed (111: Connection refused) while connecting to upstream

原因:Java 应用启动慢于 Nginx(典型如 Spring Boot 初始化需 10s+),Nginx 启动即尝试连接localhost:8080,此时 Java 进程尚未 ready。
解决:在nginx.conf的upstream块中加入健康检查与重试:

upstream java_backend { server 127.0.0.1:8080 max_fails=3 fail_timeout=30s; keepalive 32; }

并在location块中加proxy_next_upstream error timeout http_500 http_502 http_503 http_504;,让 Nginx 在首次失败后自动重试。

5.4 现象:容器内中文日志显示为?,docker exec -it nginx-java-app cat /var/log/nginx/access.log出现乱码

原因:debian:11-slim镜像默认无中文 locale,locale -a | grep zh_CN为空,导致 JavaFileWriter和 Nginxlog_format中的中文字符被截断。
解决:在Dockerfile中安装中文 locale:

RUN apt-get update && apt-get install -y locales && rm -rf /var/lib/apt/lists/* \ && localedef -i zh_CN -c -f UTF-8 -A /usr/share/locale/locale.alias zh_CN.UTF-8 ENV LANG zh_CN.UTF-8

同时 Java 启动参数加-Dfile.encoding=UTF-8(已在entrypoint.sh中体现)。


6. 验证与调优:用 3 个命令确认服务真正在跑,以及 JVM 参数怎么设才不翻车

部署完成不等于服务可用。必须用最小成本验证链路通、内容对、性能稳。这里不讲 Grafana 监控,只用curl、docker stats、jstat三个命令,覆盖从 HTTP 层到 JVM 层的黄金指标。

6.1 链路验证:curl -I http://localhost+curl http://localhost/actuator/health(若 Spring Boot)

首验 HTTP 状态码:

$ curl -I http://localhost HTTP/1.1 200 OK Server: nginx/1.24.0 Date: Tue, 15 Oct 2024 08:22:34 GMT Content-Type: text/html;charset=UTF-8

-I只取响应头,秒级返回。若返回502,立刻查 Nginx error log:docker exec nginx-java-app tail -n 20 /var/log/nginx/error.log。
次验业务健康接口(Spring Boot Actuator):

$ curl http://localhost/actuator/health {"status":"UP","components":{"diskSpace":{"status":"UP","details":{"total":52113022976,"free":42113022976,"threshold":10485760}},"ping":{"status":"UP"}}}

status: UP表示 Java 应用已就绪,且磁盘空间充足(threshold是 10MB,默认值)。

6.2 资源监控:docker stats nginx-java-app看实时 CPU/MEM,而非top

$ docker stats nginx-java-app --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}" NAME CPU % MEM USAGE / LIMIT MEM % nginx-java-app 1.23% 245.6MiB / 1.95GiB 12.35%

关键看MEM USAGE是否稳定在Xmx设置值附近(本例Xmx512m,实际使用 245MB 合理);若MEM USAGE持续逼近1.95GiB(宿主机内存),说明 JVM GC 未生效,需调参。

6.3 JVM 调优:OpenJDK 8 必加的 3 个 GC 参数,不是玄学

OpenJDK 8 默认使用 Parallel GC,吞吐量高但停顿长。容器环境下,推荐 G1 GC,需显式开启并设合理阈值:

java -Xms256m -Xmx512m \ -XX:+UseG1GC \ -XX:MaxGCPauseMillis=200 \ -XX:+PrintGCDetails \ -Xloggc:/var/log/java/gc.log \ -jar /app/app.jar
  • -XX:+UseG1GC:强制启用 G1 垃圾收集器,适合 4GB 以下堆内存;
  • -XX:MaxGCPauseMillis=200:目标 GC 停顿不超过 200ms,G1 会动态调整年轻代大小;
  • -Xloggc:将 GC 日志输出到容器内/var/log/java/,再挂载到宿主机/opt/nginx/logs/java/,便于分析。

血泪经验:某次线上服务偶发 3s 延迟,jstat -gc <pid>显示G1 Old Generation持续增长,-XX:MaxGCPauseMillis未设导致 G1 放弃停顿目标,改用ConcGCThreads=2限制并发线程数后稳定。容器内 JVM 参数不是照搬文档,而是根据docker stats的 MEM% 动态调:若MEM%长期 >70%,先加-XX:G1HeapRegionSize=1M;若 GC 频繁,再调-XX:G1NewSizePercent=30。

6.4 日志归集:/var/log/nginx/与/var/log/java/统一挂载到/opt/nginx/logs/

为方便运维查日志,我们在启动脚本中强制挂载:

-v /opt/nginx/logs:/var/log/nginx:rw \ -v /opt/nginx/logs/java:/var/log/java:rw \

这样宿主机/opt/nginx/logs/下有:

access.log # Nginx 访问日志 error.log # Nginx 错误日志 java/gc.log # JVM GC 日志 java/stdout.log # Java 标准输出(若 entrypoint.sh 重定向)

用tail -f /opt/nginx/logs/access.log即可实时跟踪请求,无需docker logs。

我坚持在每个新项目里,先跑通这个start-nginx-java.sh,再谈 CI/CD。因为它逼你直面最原始的问题:端口、权限、编码、路径、内存——这些不会因用了 K8s 就消失,只会藏得更深。当curl -I http://localhost返回200 OK的那一刻,你知道,底层是可靠的。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询