
1. 先把这件事的边界想清楚1.1 为什么要自己做一个 Oracle 19C 的 Docker 镜像真正动过手的人都知道Docker 生态里最不缺的是 MySQL、PostgreSQL 的官方镜像最缺的是 Oracle 数据库的镜像。数据库镜像仓库里能搜到的大多是第三方打包的版本杂、来源不明、出了问题没人兜底。而 Oracle 19C 又是很多存量业务系统绕不开的版本——大量老的 ERP、报表系统、中间件都认这个版本的驱动和 SQL 语法。于是用 Docker 制作 Oracle 19C 镜像这件事就从想试试变成了必须搞定。我自己第一次做这个镜像是因为要给一个十几年前的老业务做迁移验证。目标很明确本地开发机上能一键起一个 19C 实例数据能持久化脚本能自动初始化证完就删不污染宿主机。用物理机装一个 19C光是装完再打快照就得小半天还不说后面清不干净。容器化之后这件事变成了构建一次镜像之后随时 docker run。这篇文章想解决的问题很具体怎么从零开始把一个能跑起来、能建库、能持久化、能被应用连上的 Oracle 19C 装进一个 Docker 镜像里。适合三类人看一是手上有个老系统必须跑 19C、但不想在被窝里装数据库的后端二是在做 CI/CD 流水线需要把数据库当成一次性的测试依赖拉起来再销毁的工程同学三是想搞清楚数据库容器化到底特殊在哪、为什么它不像 Redis 那样一条 run 命令就完事的技术好奇心玩家。需要先明确一个现实数据库镜像和普通应用镜像完全是两回事。Nginx 镜像里装的是几十兆的二进制启动就是起个进程Oracle 19C 的完整安装是几个 GB 的体量安装过程要跑一大堆脚本、要算内核参数、要分配共享内存、要建一堆控制文件和数据文件。所以制作镜像这件事重点不在docker build那几条指令而在于安装过程能不能在非交互环境下跑通以及启动时能不能自动完成建库。1.2 三条技术路线的取舍真要做这件事摆在你面前的路其实只有三条选错了会浪费好几天。第一条是直接用官方提供的构建脚本。Oracle 在自己的代码仓库里维护了一套docker-images工程里面有针对不同版本数据库的 Dockerfile 和构建脚本。你只需要把官方发布的安装介质一个几 GB 的 zip放进指定目录跑一条脚本镜像就出来了。这条路的好处是省心、经过官方验证、启动脚本、健康检查、初始化目录约定都是现成的坏处是镜像体积大、构建时间长、对宿主机资源有硬性要求而且脚本里做了很多默认假设想改点东西得先读懂它。第二条是手工写 Dockerfile从基础系统镜像开始覆盖安装介质里的静默安装silent install自己建库、自己配监听。这条路灵活性最高可以把镜像压到最小可以定制字符集、内存参数、补丁级别代价是你要对 Oracle 的安装细节足够熟悉否则会卡在安装程序报了一个看不懂的错误上。第三条是拿第三方打包好的镜像直接用。这条路最省事但也最不可控你不知道里面装了什么补丁、启动脚本改了什么、有没有后门、版本对不对得上。生产相关的验证一律不建议走这条路学习玩玩还行。我的建议是第一次做走第一条先把能用跑通建立对整体流程的直觉等你知道了镜像里每一层是什么、启动脚本干了什么再走第二条做定制和瘦身。下面两章分别讲这两条路你可以按需跳读。1.3 先把宿主机的账算清楚动手之前有几个数字必须先算明白不然你会在构建到 80% 的时候被磁盘不足打断。项目最低要求建议值为什么磁盘可用空间40 GB80 GB 以上安装介质约 2.8 GB镜像约 7 GB构建中间层、数据文件、日志都要占空间内存4 GB8 GB 以上数据库实例最小可跑 1 GB SGA但加上系统开销和构建过程4 GB 以下会非常痛苦交换分区2 GB与内存等量建库阶段内存峰值高swap 不足会直接导致建库失败CPU 核心24静默安装大量是单线程但构建和解压能并行镜像体积这块要有心理预期19C 的企业版完整安装光$ORACLE_HOME目录就是 4~5 GB加上基础系统镜像、安装临时文件最终镜像在 7 GB 上下是正常的。如果你看到某个号称19C 精简版 800 MB的镜像那基本可以肯定里面的组件被裁掉了跑跑查询可以跑生产相关的东西风险很大。还有一点容易忽略Docker 的默认存储驱动和数据目录位置。如果你的/var/lib/docker在根分区上而根分区只有 50 GB构建两个版本就满了。动手前先df -h看一眼必要时把 Docker 的>cd OracleDatabase/SingleInstance/dockerfiles ./buildContainerImage.sh -v 19.3.0 -e跑起来之后你会看到脚本先检查 zip 是否存在、大小对不对、校验和是否匹配然后开始docker build。整个过程在普通开发机上大概 15~40 分钟取决于磁盘 I/O。机械硬盘会明显更慢这不是玄学是因为安装过程要往镜像层里写几万个文件。2.3 构建过程里 Dockerfile 到底干了什么读懂 Dockerfile 能帮你在出错时快速定位。19.3.0 的 Dockerfile 逻辑大致是这样一条链第一部分基于一个精简的 Linux 基础镜像安装必要工具unzip、libaio、bc、net-tools等创建oinstall和dba用户组与oracle用户把内核参数和资源限制写进配置文件。这一步是把 Oracle 安装的环境先摆好包括kernel.shmmax、kernel.shmall、fs.file-max、net.ipv4.ip_local_port_range这些经典参数以及oracle用户的nofile、nproc限制。第二部分把安装介质COPY进镜像并解压到$ORACLE_HOME。这里有个细节19C 开始安装介质解压出来不再是安装程序而是已经安装好的软件家目录只需要跑一次runInstaller -silent完成配置而不是像 11g 那样完整地装一遍。这是 19C 容器化能这么干的前提。第三部分跑runInstaller -silent -waitforcompletion完成软件安装然后以 root 身份执行$ORACLE_HOME/root.sh。这一步如果失败镜像构建就在这里断掉日志会留在构建输出里。第四部分把container-entrypoint.sh、createDB.sh、checkDBStatus.sh这些运行期脚本拷进去声明VOLUME /opt/oracle/oradata声明EXPOSE 1521 5500设置HEALTHCHECK最后ENTRYPOINT指向那个入口脚本。把这几部分对上号你就能理解为什么这个镜像启动慢软件是构建时装好的但数据库是启动时才建出来的。镜像里其实没有ORCLCDB这个库只有一堆空目录。第一次docker run时入口脚本发现数据目录是空的才会去调用dbca建库。这就是为什么官方给的健康检查start-period设了整整 30 分钟——建库确实可能要跑十几分钟。2.4 构建完成后先看一眼镜像本身构建结束docker images里会出现一个类似oracle/database:19.3.0-ee的镜像。这时候别急着 run先做两件事。第一件看体积和分层docker history --no-trunc oracle/database:19.3.0-ee | head -20你会看到体积最大的一层是解压安装介质那一层通常 4 GB 以上。这一层没法压缩因为它是实打实的文件。理解这一点很关键后面所有关于镜像瘦身的努力主要战场都在这一层之外——清理临时文件、去掉 install 目录、合并 RUN 指令。第二件进容器看看目录结构确认ORACLE_HOME、ORACLE_BASE的位置对不对docker run --rm -it --entrypoint /bin/bash oracle/database:19.3.0-ee进去之后ls /opt/oracle能看到product/19c/dbhome_1说明软件装好了看到oradata是空的说明库还没建。这一步验证的是软件层面 OK把启动期的问题和构建期的问题隔离开后面排查能少走一半弯路。3. 手工写 Dockerfile把每一步都捏在自己手里3.1 什么时候该走手工这条路官方脚本能做出来的镜像为什么还要手工做一个我遇到的实际需求有三类。一是要控制字符集和建库参数。官方脚本的建库脚本虽然支持通过环境变量传ORACLE_CHARACTERSET、ORACLE_SID、ORACLE_PDB但有些更细的参数比如dbca的 block size、national character set、redo 日志大小、TEMP 表空间配置它没暴露出来你要么改脚本要么自己建。二是要打补丁。业务系统要求某个特定补丁级别官方镜像包不带你得在构建过程里把补丁打进去。手工 Dockerfile 里加一个opatch apply的步骤就行。三是要瘦身。官方镜像里有不少开发验证用不到的东西某些可选组件、安装日志、示例 schema手工构建可以按需裁掉把 7 GB 压到 5 GB 以内对 CI 环境拉镜像的速度影响很大。手工构建的核心难点只有一个把交互式的图形安装变成一条能在 Dockerfile 里跑完的非交互命令。这个靠响应文件response file解决。3.2 Dockerfile 骨架与关键指令下面这份骨架是我实际用过、跑通的版本做了适当裁剪。它不是最优解但它把每个阶段都拆开了方便你出问题时定位。FROM oraclelinux:7-slim ENV ORACLE_BASE/opt/oracle \ ORACLE_HOME/opt/oracle/product/19c/dbhome_1 \ ORACLE_SIDORCLCDB \ PATH/opt/oracle/product/19c/dbhome_1/bin:$PATH \ LD_LIBRARY_PATH/opt/oracle/product/19c/dbhome_1/lib # 1. 装基础依赖 preinstall 包如果有可用 yum 源就用它省事 RUN yum install -y oracle-database-preinstall-19c \ yum install -y unzip vim tar net-tools which \ yum clean all \ rm -rf /var/cache/yum # 2. 准备目录和权限 RUN mkdir -p $ORACLE_BASE/product/19c \ mkdir -p $ORACLE_BASE/oraInventory \ mkdir -p $ORACLE_BASE/oradata \ mkdir -p $ORACLE_BASE/install \ chown -R oracle:oinstall $ORACLE_BASE \ chmod -R 775 $ORACLE_BASE # 3. 解压安装介质到 ORACLE_HOME COPY --chownoracle:oinstall LINUX.X64_193000_db_home.zip /tmp/db_home.zip USER oracle RUN cd $ORACLE_HOME unzip -q /tmp/db_home.zip rm -f /tmp/db_home.zip # 4. 静默安装软件 COPY --chownoracle:oinstall db_install.rsp /tmp/db_install.rsp RUN $ORACLE_HOME/runInstaller -silent -waitforcompletion \ -responseFile /tmp/db_install.rsp \ -ignorePrereqFailure \ rm -f /tmp/db_install.rsp # 5. root 脚本必须在 root 下跑 USER root RUN $ORACLE_HOME/root.sh # 6. 运行期脚本 COPY --chownoracle:oinstall entrypoint.sh /opt/oracle/entrypoint.sh RUN chmod x /opt/oracle/entrypoint.sh VOLUME [/opt/oracle/oradata] EXPOSE 1521 5500 USER oracle ENTRYPOINT [/opt/oracle/entrypoint.sh]oracle-database-preinstall-19c这个包非常值得用它把创建用户组、设置内核参数、配置资源限制这些琐碎活儿全包了。如果没有可用的 yum 源就得手工groupadd oinstall、groupadd dba、useradd -g oinstall -G dba oracle再往/etc/sysctl.conf和/etc/security/limits.conf里写参数。手工写的时候要特别注意两点kernel.shmmax要大于等于 SGA 大小fs.aio-max-nr不要超过系统上限否则安装程序的预检查会报错——虽然-ignorePrereqFailure能强制跳过但跳过的后果会在建库阶段以更难看的方式出现。3.3 静默安装的响应文件几个字段决定成败响应文件是手工构建的核心。它就是一个 keyvalue 的文本文件告诉安装程序别问我按这个来。下面这几个字段是必填且容易写错的oracle.install.optionINSTALL_DB_SWONLY UNIX_GROUP_NAMEoinstall INVENTORY_LOCATION/opt/oracle/oraInventory ORACLE_HOME/opt/oracle/product/19c/dbhome_1 ORACLE_BASE/opt/oracle oracle.install.db.InstallEditionEE oracle.install.db.OSDBA_GROUPdba oracle.install.db.OSOPER_GROUPdba oracle.install.db.OSBACKUPDBA_GROUPdba oracle.install.db.OSDGDBA_GROUPdba oracle.install.db.OSKMDBA_GROUPdba oracle.install.db.OSRACDBA_GROUPdba oracle.install.db.rootconfig.executeRootScriptfalseINSTALL_DB_SWONLY的意思是只装软件不建库。这是关键建库放到容器启动时做这样同一个镜像可以用不同的ORACLE_SID起多个库也可以把数据目录挂出来持久化。executeRootScriptfalse也很关键。在 Dockerfile 里安装程序是以oracle用户跑的它没法切到 root所以必须让它跳过自动执行root.sh改成我们自己用USER root单独跑一遍。我第一次没设这个安装跑到最后一步卡住报权限错误查了半天。再有一点安装过程会产生大量日志默认落在$ORACLE_BASE/oraInventory/logs和/tmp下。这些日志在构建阶段看着有用进了镜像就是纯垃圾可以在同一个RUN里顺手删掉。注意必须写在同一条 RUN 指令里分成两条的话前面那条产生的文件已经被固化成镜像层了后面删也删不掉体积。3.4 入口脚本容器启动时到底要做什么入口脚本是整个镜像的大脑它要做三件事判断是否需要建库、需要的话就建、建完启动监听并保持前台运行。逻辑大致是这样#!/bin/bash set -e # 如果数据目录里已经有控制文件说明库已经建过了直接启动 if [ -e $ORACLE_BASE/oradata/$ORACLE_SID/system01.dbf ]; then echo Database already exists, starting up... $ORACLE_HOME/bin/sqlplus -s / as sysdba EOF startup; exit; EOF else echo No database found, creating... /opt/oracle/createDB.sh fi # 跑一次性的 setup 脚本 for f in /opt/oracle/scripts/setup/*.sh; do [ -e $f ] || continue echo Running setup script: $f $f done mv /opt/oracle/scripts/setup /opt/oracle/scripts/setup.done # 跑每次启动都要跑的 startup 脚本 for f in /opt/oracle/scripts/startup/*.sh; do [ -e $f ] || continue $f done # 保持容器不退出 tail -f $ORACLE_BASE/diag/rdbms/${ORACLE_SID,,}/$ORACLE_SID/trace/alert_$ORACLE_SID.log这里面有几个我从踩坑里学来的细节。判断库是否已存在不要用目录是否为空要用是否存在system01.dbf。因为建库过程中会先创建目录和一堆临时文件如果中途失败目录非空但库不完整用目录非空判断会导致下次启动直接跳过建库、然后启动失败进入一个永远起不来的死循环。setup脚本执行完要改名成setup.done。这是官方脚本的做法很聪明setup只应该跑一次比如建业务用户、导入初始数据而startup每次启动都要跑比如开启某个会话级配置。如果不改名你每次重启容器都会重复执行建用户脚本第二次就会报用户已存在。最后保持容器运行别用sleep infinity用tail -f盯着 alert 日志。这样docker logs能看到数据库的真实运行日志出问题时不用再docker exec进去翻文件体验好很多。4. 启动容器参数里每一项都有原因4.1 一条能直接抄的 run 命令镜像有了跑起来这一条docker run -d \ --name ora19c \ --shm-size2g \ -p 1521:1521 \ -p 5500:5500 \ -e ORACLE_PWDYourStrong#Passw0rd \ -e ORACLE_CHARACTERSETAL32UTF8 \ -e TZAsia/Shanghai \ -v /data/oracle/oradata:/opt/oracle/oradata \ -v /data/oracle/scripts/setup:/opt/oracle/scripts/setup \ --restart unless-stopped \ oracle/database:19.3.0-ee逐个说为什么。--shm-size2g这是最容易漏、后果最严重的一项。Docker 默认给容器的/dev/shm只有 64 MB而 Oracle 的实例启动、建库、甚至某些查询都会用到共享内存段。不给这个参数你会看到建库跑到一半报ORA-00845: MEMORY_TARGET not supported on this system或者实例起来后一查大表就崩。2 GB 是个稳妥值如果你打算给 SGA 配 4 GB这个值也要跟着往上调经验公式是shm-size ≥ SGA 大小。ORACLE_PWD不设置这个变量容器会直接拒绝启动并打印提示。密码策略默认要求一定复杂度我试过用纯数字密码结果建库阶段失败日志里能看到密码校验不通过。写密码时建议用单引号包起来密码里带$、!、#这些字符时双引号会被 shell 提前展开。ORACLE_CHARACTERSET只在首次建库时生效建完之后改这个变量没有任何作用。这一点极其重要——字符集是建库时写死在数据字典里的事后想改只能重建库或者做全库导出导入。所以第一次跑之前就想清楚需要中文就AL32UTF8现在基本都用这个不要图省事用默认的WE8MSWIN1252。TZ设了之后容器的date才是本地时间。但这个变量有个隐藏前提镜像里得有对应的时区数据文件。精简基础镜像常常没装tzdata设了TZ也不生效表现出来就是数据库日志时间比本地时间差 8 小时。稳妥做法是在 Dockerfile 里补一句安装tzdata或者干脆在容器里手动ln -sf时区文件。-v挂数据目录这是所有数据库容器的生命线。不挂载的话容器一删你建的库、导的数据全没了而且这个过程没有任何确认提示。挂载之后docker rm只是删掉容器本身数据还在宿主机上重新 run 一个新容器挂同一个目录就能接着用。4.2 首次启动的十几分钟日志里在发生什么docker run之后立刻docker logs -f ora19c你会看到一段持续十几分钟的输出。理解这段输出能让你在它卡住的时候知道该不该慌。前一两分钟是初始化环境创建密码文件、准备目录、检查内存和共享内存。这一段如果报错基本都是shm-size或者内存不足的问题。中间一大段是dbca建库创建控制文件、创建 system/sysaux/undo 表空间、创建数据字典、编译内置存储过程。这一段最慢在机械盘上跑二十分钟都正常。这时候docker logs可能会长时间没有新输出别急着重启去宿主机上docker stats看一眼如果 CPU 和 I/O 还在动说明它活着。最后几分钟是配置监听、注册服务、跑setup脚本。看到类似Database is ready to use或者监听器ready的字样就说明成了。有个很实用的观察点建库期间可以docker exec -it ora19c bash进去tail -f /opt/oracle/cfgtoollogs/dbca/ORCLCDB/ORCLCDB.log这个日志比容器 stdout 详细得多能看到dbca具体卡在哪一步。我遇到过一次建库卡了四十分钟进这个日志一看是在创建 TEMP 表空间时报空间不足宿主机磁盘满了。如果只看 stdout你只会看到它一直在转。4.3 连上去验证别只看容器状态容器Up不代表数据库能用。健康检查通过也不代表业务能连。我一般做三层验证。第一层容器内本地连docker exec -it ora19c bash -lc sqlplus / as sysdba进去之后select status from v\$instance;返回OPEN才算实例正常。如果返回MOUNTED或者报错说明实例起来了但库没打开通常是启动脚本里startup那句没执行成功。第二层容器内通过监听连docker exec -it ora19c bash -lc \ sqlplus system/YourStrong#Passw0rd//localhost:1521/ORCLPDB1这一步验证的是监听器和服务名注册。本地/ as sysdba走的是操作系统认证不经过监听所以它成功不代表监听没问题。很多应用连不上但容器是好的的问题都卡在这一层。第三层宿主机通过端口映射连sqlplus system/YourStrong#Passw0rd//127.0.0.1:1521/ORCLPDB1这一步验证端口映射和宿主机防火墙。三层都过了才算真的能用。注意19C 用的是容器数据库CDB架构一个 CDB 下挂若干 PDB。应用连接时要连 PDB 的服务名上面例子里的ORCLPDB1而不是 CDB 的 SID。连错了会报ORA-12505或者连上去了发现看不到自己的表——其实你连到 CDB 的根容器了业务表在 PDB 里。5. 常见问题与排查实录5.1 构建阶段最常卡住的三个地方构建跑到解压那一步报错。九成是安装介质的问题下成了 Windows 版、下到一半断了导致 zip 不完整、或者文件名被改了导致脚本找不到。校验方法很简单unzip -t LINUX.X64_193000_db_home.zip跑一遍能正常列出文件清单才算完整。另外注意磁盘空间解压 3 GB 的 zip 需要 5 GB 以上的临时空间空间不够时unzip报的错误信息非常隐晦。安装程序报预检查失败。常见的是kernel.shmmax太小、可用内存低于要求、fs.file-max不够。在 Docker 里/etc/sysctl.conf里写的参数在构建阶段是不生效的——容器的内核参数由宿主机控制镜像里写配置文件只是给以后用。所以预检查里的内核参数项如果不打算在 run 的时候加--sysctl直接用-ignorePrereqFailure跳过。但内存和磁盘这两项不能跳跳了后面必然失败。root.sh执行报错。这个脚本必须在 root 下、且ORACLE_HOME和oraInventory的权限正确时才不报错。常见错误是前面chown只做了$ORACLE_BASE没做$ORACLE_HOME或者COPY进来的文件属主变成了 root。用COPY --chownoracle:oinstall能避免大半问题。5.2 启动阶段报错信息与真实原因的对照这一块我整理成一张表都是实际遇到过的。现象 / 报错大概率原因处理方式容器启动几秒就退出日志提示密码未设置没传ORACLE_PWD加上环境变量注意特殊字符用单引号ORA-00845: MEMORY_TARGET not supported/dev/shm太小加--shm-size2g或更大建库跑到一半失败日志里有空间相关错误宿主机磁盘满df -h检查清理或扩容后重跑建库失败后重启容器反复退出数据目录残留半成品脚本判断逻辑有误清空挂载目录重新建或改用system01.dbf判断建库失败提示内存不足宿主机内存不够或 swap 未开加内存、加 swap或降低建库内存参数容器 Up但应用连不上监听未起 / 服务名写错 / PDB 未打开按 4.3 的三层验证逐层排查连上了但看不到表连到了 CDB 而不是 PDB服务名改成 PDB 的服务名日志时间差 8 小时TZ未生效装tzdata或在容器内建时区软链查询大表时容器被杀内存超限被 OOM Killer 干掉限制 SGA 大小加--memory并留出余量docker logs长时间无输出但没退出建库正在进行正常现象看docker stats和 dbca 日志确认关于建库失败后反复退出这一条我再多说两句因为这是最坑的。数据库建库是个不可逆的过程跑到一半失败会在数据目录留下一堆半成品文件。如果入口脚本用目录非空来判断库已存在下次启动就会跳过建库、直接startup然后因为数据文件不完整而失败容器退出重启策略又把它拉起来形成无限循环。表现出来就是docker logs里反复刷同样的错误一晚上能把日志刷到几个 GB。正确的处理方式是第一次建库失败先docker rm -f掉容器把挂载目录整个清空查清楚失败原因看 dbca 日志再重新跑。不要指望它自己恢复。5.3 使用阶段的几个坑数据持久化了但配置没持久化。只挂载了oradata目录的话你后续改的listener.ora、tnsnames.ora、sqlnet.ora都在容器内容器一删就没了。如果这些配置是业务必需的要么把它们也挂出来要么写成setup脚本在首次启动时自动生成。容器重启后实例能起但业务连不上。有一种情况是监听器的配置在容器重启后没跟着起来。检查lsnrctl status如果监听没起在startup脚本里加一句lsnrctl start。官方镜像的入口脚本处理了这一点手工构建的容易漏。备份忘了做。容器化之后很多人会误以为有 Docker volume 就是安全的其实 volume 只是存在宿主机上宿主机磁盘挂了照样全没。数据库该做的逻辑备份、RMAN 备份一样都不能少只是执行方式变成了docker exec或者起一个专职的备份容器。我先在一个测试环境里吃过亏docker volume prune一条命令下去把几个挂载的 volume 当成悬空卷清掉了那次之后我在所有容器的 run 命令里都加了明确的-v路径绝不用匿名卷。5.4 独家小技巧验证镜像是否真的可用我有个习惯每次做出一个新镜像不急着用到项目里先跑一个最小可用性验证起一个不挂载任何目录的临时容器等它建完库连进去建一张表、插一条数据、查出来然后docker rm -f。这个过程能同时验证镜像构建正确、启动脚本逻辑正确、数据库功能正常三件事耗时十几分钟但能省掉后面在项目环境里排查的几小时。还有个更省事的做法把这个验证过程写成一个 shell 脚本每次构建完自动跑一遍失败就报警。这个脚本本身只有二十来行但它在我们的流水线里挡掉过至少三次看起来构建成功、实际起不来的坏镜像。6. 镜像瘦身与长期维护6.1 体积从 7 GB 压到 5 GB砍的是什么镜像体积这件事做之前要先想明白省下来的空间到底是给谁省的。如果是本地开发用7 GB 和 5 GB 区别不大硬盘够如果是 CI 流水线每次 Job 都要拉一次镜像那 2 GB 的差距可能意味着每次构建多等两分钟一天几百次就是几小时的浪费这时候瘦身就很有价值。能砍的东西按收益排序第一是安装临时文件和解压出来的 zip。$ORACLE_HOME解压之后/tmp下的 zip 就没用了$ORACLE_BASE/oraInventory/logs里的安装日志也没用了。这两块能省 2~3 GB。关键是在同一个 RUN 指令里清理否则清不掉。第二是$ORACLE_HOME/install目录。这个目录里有安装时的响应文件、日志、临时脚本装完之后运行期基本用不到。但要注意官方镜像的入口脚本里可能会引用其中的文件删之前先grep一遍启动脚本确认没依赖。第三是可选组件。$ORACLE_HOME下有一些开发验证用不到的组件目录可以用chopt工具关掉。但这个操作有风险关错了会导致某些功能报错建议只关明确不用的。我实测过一个版本清理临时文件 删除 install 目录镜像从 7.2 GB 降到 5.6 GB启动和功能都正常。再往下压就要动组件了收益变小、风险变大不划算。6.2 数据目录的规划比镜像更重要很多人把注意力全放在镜像上其实挂载目录的规划才是长期用得舒服的关键。我的习惯是按环境分开/data/oracle/dev/oradata # 开发环境数据 /data/oracle/dev/scripts # 开发环境初始化脚本 /data/oracle/test/oradata # 测试环境每个环境一套独立的目录容器名也带上环境前缀。这样同一台机器上可以同时跑好几个不同版本、不同ORACLE_SID的实例互不干扰出问题也好定位。目录命名一定要有规律我见过用容器 ID 当目录名的容器一删就再也对不上号了。另外提醒一句挂载目录的属主和权限要提前设好。宿主机上建的目录默认属主是 root容器里的oracle用户uid 通常是 54321写不进去表现出来就是建库时报无法创建数据文件。处理方式有两种chown 54321:54321把目录给容器里的用户或者 run 的时候加--user。前者简单推荐。6.3 版本升级时的重建策略19C 的小版本升级在容器里做和在物理机上做思路不一样。物理机上你会opatch apply打个补丁容器里的常规做法是构建一个新镜像然后用新镜像起新容器挂载同一份数据目录。这要求两件事新镜像的ORACLE_HOME路径和老的一致数据字典版本兼容。这个流程里有个必须注意的点先用老镜像把数据库正常关闭再用新镜像启动。直接docker kill然后用新镜像起数据库处于异常关闭状态起来之后要跑实例恢复虽然一般能恢复成功但如果你正好在做升级这个不确定性没必要引入。关闭的命令是docker exec -it ora19c bash -lc \ sqlplus / as sysdba shutdown immediate; exit;确认实例已经关闭docker logs里能看到 shutdown 完成的日志再docker rm掉老容器用新镜像 run 一个挂同一个数据目录的。这个顺序不能反。至于大版本跨代升级比如 11g 到 19C容器化帮不上太多忙该走的dbupgrade流程一样要走只是可以在容器里做做完把数据目录打包出来给新镜像用。这种情况下我一般不用挂载原目录的方式而是导出导入或者用数据泵干净利落出了问题也好回退。最后分享一个我在这个项目里形成的习惯每做一个数据库镜像就在仓库里配一份 README写清楚这个镜像是怎么构建的、参数是什么、第一次启动要多久、挂载哪些目录、验证命令是哪几条。听起来很啰嗦但半年后你要重建一个一模一样的镜像时那几分钟的文档能省掉你半天时间。镜像本身是死的构建过程才是资产。