基于 Docker 的 C++ 开发环境搭建与 CLion 集成教程
本教程完整记录"制作 C++ 开发 Docker 镜像 → 集成到 CLion"的全过程。
文中所有【自定义:...】标记处均可按需修改,其余命令经过实测可直接使用。
SSH 远程访问、gdb 版本定制属于可选附加操作,作为附录放在文末。
目录
- 总体架构
- 创建 Fedora 开发容器
- 安装 C++ 工具链
- 安装其他工具
- 镜像持久化与导出
- 在 CLion 中配置 Docker 环境
- C++23 模块配置
- 常见问题排查
- 附录:完整命令速查表
- 附录A(可选附加):配置 SSH 远程访问
- 附录B(可选附加):定制 gdb 版本(源码编译)
- 结语:教程完结后的清理
1. 总体架构
┌─────────────────────────────────────────────────────┐ │ 宿主机 │ │ │ │ ├── Docker │ │ │ └── 开发容器 (fedora44-cpp-dev) │ │ │ ├── GCC/Clang/CMake/Ninja/GDB 工具链 │ │ │ └── 挂载 ~/cpp-projects → /workspace │ │ │ │ │ ├── CLion (IDE) │ │ │ └── Docker 工具链 → 容器内编译运行 │ │ └── (可选) 其他机器 → SSH 远程连接容器 (见附录A) │ └─────────────────────────────────────────────────────┘核心思想:开发环境全部装在 Docker 容器里,宿主机只保留 IDE 和代码;同一套镜像可在任何机器上复现,便于团队统一环境。
💡 教程结束后,所有容器都可以停用甚至删除,只保留镜像即可随时重建(详见结语)。
2. 创建 Fedora 开发容器
2.1 准备基础镜像
# 拉取基础镜像(若网络受限可换国内镜像源)dockerpull fedora:44# 【自定义:可换成 ubuntu:24.04 / debian:12 等其他发行版,后续命令中的包管理器需相应调整(apt 等)】💡 实测提示:本机直连 Docker Hub 可能失败(
connection reset by peer),可改用镜像源,例如:dockerpull docker.1ms.run/library/fedora:44dockertag docker.1ms.run/library/fedora:44 fedora:44
2.2 创建并启动容器
# 创建代码挂载目录(宿主机)mkdir-p~/cpp-projects# 【自定义:容器名 fedora44-cpp-dev、挂载目录 ~/cpp-projects、主机名 cpp-dev 均可改】dockerrun-d\--namefedora44-cpp-dev\-hcpp-dev\-v~/cpp-projects:/workspace\fedora:44\sleepinfinity# 查看容器状态dockerps--filtername=fedora44-cpp-dev| 参数 | 说明 |
|---|---|
--name | 容器名【自定义】 |
-h | 容器主机名 |
-v | 挂载目录:宿主机~/cpp-projects↔ 容器/workspace(代码持久化) |
💡 这里用
sleep infinity保持容器运行,后续在容器内通过docker exec操作即可。若需要 SSH 远程访问,见附录A。
3. 安装 C++ 工具链
3.1 安装编译工具
dockerexecfedora44-cpp-dev dnfinstall-y\gcc gcc-c++makecmake ninja-build\gdbgitvalgrind cppcheck clang3.2 验证版本
dockerexecfedora44-cpp-devsh-c' g++ --version | head -1 clang++ --version | head -1 cmake --version | head -1 ninja --version gdb --version | head -1 '3.3 编译测试(可选)
dockerexecfedora44-cpp-devsh-c' echo "int main(){return 0;}" > /workspace/t.cpp g++ -std=c++20 -o /workspace/t /workspace/t.cpp && echo "工具链正常" rm -f /workspace/t /workspace/t.cpp '3.4 工具链自定义空间
- 【自定义:需要 Boost / OpenCV / Eigen 等库时,追加安装】:
dockerexecfedora44-cpp-dev dnfinstall-yboost-devel opencv-devel eigen3-devel - 【自定义:需要 Python 开发头文件时】:
dockerexecfedora44-cpp-dev dnfinstall-ypython3-devel
4. 安装其他工具
4.1 rsync(文件同步)
dockerexecfedora44-cpp-dev dnfinstall-yrsyncdockerexecfedora44-cpp-devrsync--version|head-1# 3.5.0常用场景:
# 宿主机 ↔ 容器文件同步(容器需已配置 SSH,见附录A)rsync-avz-e"ssh -p 2222"~/cpp-projects/ root@localhost:/workspace/# 容器内目录同步dockerexecfedora44-cpp-devrsync-av--delete/workspace/ /backup/4.2 ninja(快速构建工具)
# ninja-build 通常在装 CMake 工具链时已附带,验证即可:dockerexecfedora44-cpp-dev ninja--version# 1.13.2# 若未安装:docker exec fedora44-cpp-dev dnf install -y ninja-build5. 镜像持久化与导出
⚠️ 容器是"一次性"的:容器内的更改(装软件、改配置)默认只存在于运行中的容器(可写层)。
若删除容器重建,这些更改会丢失。因此完成配置后要提交为镜像——镜像才是真正的"持久化产物"。
5.1 提交为镜像
# 把当前容器状态保存为新镜像# 【自定义:标签名 gdb171 可改成任意名称,如 v1 / latest】dockercommit fedora44-cpp-dev fedora44-cpp-dev:gdb171# 之后每次改动配置,重新 commit 覆盖即可dockercommit fedora44-cpp-dev fedora44-cpp-dev:gdb1715.2 导出镜像为 tar(分享/迁移)
# 导出(docker save 保留完整镜像结构,可原样 load 复现)# 【自定义:输出路径和文件名】dockersave-o~/Desktop/fedora44-cpp-dev.tar fedora44-cpp-dev:gdb171ls-lh~/Desktop/fedora44-cpp-dev.tar# 约 1.6GB💡 常用镜像仓库名写法:
docker save -o xxx.tar 仓库名:标签名
5.3 在另一台机器加载镜像
# 加载镜像(创建容器的方法见第 2 章,按需创建即可)dockerload-ifedora44-cpp-dev.tardockerimages|grepfedora44-cpp-dev5.4 压缩导出(节省空间,可选)
dockersave fedora44-cpp-dev:gdb171|gzip>~/Desktop/fedora44-cpp-dev.tar.gz# 加载时自动解压:gunzip-cfedora44-cpp-dev.tar.gz|dockerload5.5 自定义空间
- 【自定义:推送到远程仓库(如私有 registry / Docker Hub)】:
dockertag fedora44-cpp-dev:gdb171 your-registry.com/dev/fedora44-cpp-dev:latestdockerpush your-registry.com/dev/fedora44-cpp-dev:latest
6. 在 CLion 中配置 Docker 环境
CLion 通过Docker 工具链把"编译、运行、调试"都放进容器执行,宿主机只当编辑器。
前提:已按第 2~3 章准备好镜像(如fedora44-cpp-dev:gdb171)。
6.1 配置 Docker 工具链
- 打开Settings(
Ctrl+Alt+S)→Build, Execution, Deployment→Toolchains - 点击+→ 选择Docker
- 配置:
- Image:选择镜像
fedora44-cpp-dev:gdb171(或填写自定义镜像名) - CMake:选择容器内的 cmake(CLion 一般自动检测)
- 点击“Docker” 旁的复制图标→ CLion 自动检测容器内编译器(gcc/g++/gdb 等)
- Image:选择镜像
- 检测结果应显示容器内的GCC 16.2、GDB 17.1等
💡 若 CLion 未自动检测,可在Credentials配置 SSH 连接(host:localhost, port:2222,需先完成附录A)让 CLion 通过 SSH 进容器检测。
6.2 配置 CMake Profile
- Settings→Build, Execution, Deployment→CMake
- 点击+新建 Profile(如
Debug-Docker):- Toolchain:选择上一步的 Docker 工具链
- Build directory:
cmake-build-debug-docker(或默认) - CMake options:留空(模块配置已写在 CMakeLists.txt,见第 7 章)
- 确定后,CLion 会调用容器内的 CMake 生成构建文件
6.3 加载并构建项目
- 打开项目(如
mcpp/lesson1) - 右下角选择Debug-Docker配置
- 点击Reload CMake Project(或自动触发)
- 点击Run / Debug按钮 → 构建过程在容器内完成
6.4 项目结构建议
mcpp/ # 课程总目录 ├── CMakeLists.template.txt # 复用的 CMake 模板(见第 7 章) └── lesson1/ ├── CMakeLists.txt # 从模板复制 └── main.cpp # import <iostream>; 等每个新课程:复制CMakeLists.template.txt→ 改名CMakeLists.txt→ 改project()名和目标名。
6.5 自定义空间
- 【自定义:IDE 也可用 VS Code + Remote-SSH 方案】,配置:
然后 VS Code 安装Remote-SSH插件连接# ~/.ssh/config Host cpp-dev HostName 172.16.29.124 # 【自定义】 Port 2222 User cppdev # 【自定义】cpp-dev即可(需先完成附录A)。
7. C++23 模块配置
若课程需要使用 C++20/23 模块语法(
import),本节提供两种方案。
推荐使用"头文件单元"(方案一),因为 CLion 的代码分析器无法识别import std;(std 模块接口未参与索引),
编辑器会持续报std未定义的红色语法错误,影响开发体验;而头文件单元import <header>;能被 CLion 正确解析,无语法误报。
7.1 问题根源
import <iostream>;(头文件单元)或import std;都需要先预编译生成.gcm文件,否则编译报错:
error: failed to read compiled module: No such file or directory此外,CMake 对 GCC 的模块支持不完整,需要关闭 CMake 模块扫描,让 GCC 直接从gcm.cache解析。
7.2 方案一(推荐):头文件单元import <header>;
用add_header_units()函数一条命令自动预编译所有列出的头文件单元,新增库只需往参数里加一个名字。
CMakeLists.txt 模板(已实测通过):
cmake_minimum_required(VERSION 3.28) project(lesson CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) add_compile_options(-fmodules-ts) # 关键:关闭 CMake 模块扫描,由 GCC 直接从构建目录 gcm.cache 解析 set(CMAKE_CXX_SCAN_FOR_MODULES OFF) # ===== 头文件单元自动预编译函数 ===== # 用法: add_header_units(iostream vector string map ...) function(add_header_units) set(_headers ${ARGN}) add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/header_units.done COMMAND ${CMAKE_CXX_COMPILER} -std=c++20 -fmodules-ts -x c++-system-header -c ${_headers} COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/header_units.done WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT "Precompiling header units: ${_headers}" ) add_custom_target(header_units DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/header_units.done) endfunction() # 使用:把课程用到的标准库头文件列一次即可 add_header_units(iostream vector string map algorithm memory) add_executable(lesson main.cpp) add_dependencies(lesson header_units)源码用法:
import<iostream>;// 用到的头文件,逐个 importimport<vector>;import<string>;intmain(){std::cout<<"Hello"<<std::endl;std::vector<int>v{1,2,3};}7.3 方案二(备选):import std;
一条预编译覆盖整个标准库,但CLion 会报语法错误(编辑器无法识别std),仅编译能通过。
CMakeLists.txt 模板(已实测通过):
cmake_minimum_required(VERSION 3.28) project(lesson CXX) set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) add_compile_options(-fmodules-ts) set(CMAKE_CXX_SCAN_FOR_MODULES OFF) # 自动定位 GCC 的 std 模块源码(兼容 GCC 版本升级) file(GLOB _std_cc_candidates "/usr/include/c++/*/bits/std.cc" "/usr/lib/gcc/*/*/include/c++/*/bits/std.cc") list(GET _std_cc_candidates -1 _std_cc) # 预编译 std 模块(每个构建目录只执行一次) add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/std_prebuilt.done COMMAND ${CMAKE_CXX_COMPILER} -std=c++23 -fmodules-ts -x c++ -c ${_std_cc} COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/std_prebuilt.done WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT "Precompiling C++23 std module" ) add_custom_target(stdlib_prebuilt DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/std_prebuilt.done) add_executable(lesson main.cpp) add_dependencies(lesson stdlib_prebuilt)源码用法:
importstd;// 一次导入整个标准库intmain(){std::cout<<"Hello"<<std::endl;std::vector<int>v{1,2,3};}8. 常见问题排查
| 现象 | 原因 | 解决方法 |
|---|---|---|
docker pull报connection reset by peer | 直连 Docker Hub 网络受限 | 换国内镜像源(见 2.1) |
容器停止(Exited) | 主进程退出或手动停止 | docker start 容器名 |
importdoes not name a type | 缺-fmodules-ts或在宿主机编译 | 加-std=c++20 -fmodules-ts;必须在容器内编译 |
failed to read compiled module | 头文件单元/std 模块未预编译 | 先执行预编译(见 7.2/7.3) |
-fmodule-mapper相关报错 | CMake 模块扫描与 GCC 不兼容 | 设CMAKE_CXX_SCAN_FOR_MODULES OFF(见第 7 章) |
CMake 报No SOURCES given to target | 模块目标声明方式不对 | 使用第 7 章的模板写法,不要混用两种声明 |
| CLion 无法检测 Docker 编译器 | 工具链未配置 / 镜像未就绪 | 确认镜像存在;用 SSH credentials 检测(见 6.1) |
CLion 对import std;报红色语法错误 | CLion 分析器无法索引 std 模块 | 改用头文件单元方案(见 7.2 推荐方案) |
9. 附录:完整命令速查表
容器管理
dockerps-a# 查看所有容器dockerexec-itfedora44-cpp-devbash# 进入容器终端dockerstart / stop fedora44-cpp-dev# 启停容器dockerrm-ffedora44-cpp-dev# 删除容器(注意先 commit!)dockerimages# 查看镜像dockercommit fedora44-cpp-dev fedora44-cpp-dev:gdb171# 保存镜像常用路径
| 用途 | 宿主机 | 容器内 |
|---|---|---|
| 代码挂载 | ~/cpp-projects | /workspace |
| 导出镜像 | ~/Desktop/fedora44-cpp-dev.tar | — |
| std 模块源码(方案二用) | — | /usr/include/c++/16/bits/std.cc |
最终验证清单(全部 ✅ 表示环境就绪)
# 容器运行dockerps--filtername=fedora44-cpp-dev# 工具链dockerexecfedora44-cpp-dev g++--version|head-1# GCC 16.2dockerexecfedora44-cpp-dev gdb--version|head-1# GDB 17.2(如需 17.1 见附录B)dockerexecfedora44-cpp-dev ninja--version# 1.13.2dockerexecfedora44-cpp-devrsync--version|head-1# 3.5.0# CLion# 右下角选择 Debug-Docker 配置 → Run 按钮,能编译运行即 OK10. 附录A(可选附加):配置 SSH 远程访问
目的:让容器可以被宿主机或局域网内其他机器远程连接。仅当需要远程开发/部署时执行,本地用 CLion + Docker 工具链则不需要。
10.1 安装 OpenSSH
dockerexecfedora44-cpp-dev dnfinstall-yopenssh-server openssh-clients10.2 配置 sshd 并设置密码
dockerexecfedora44-cpp-devsh-c' ssh-keygen -A sed -i "s/^#PermitRootLogin.*/PermitRootLogin yes/" /etc/ssh/sshd_config sed -i "s/^#PasswordAuthentication.*/PasswordAuthentication yes/" /etc/ssh/sshd_config sed -i "s/^PasswordAuthentication no/PasswordAuthentication yes/" /etc/ssh/sshd_config # 【自定义:root 密码,生产环境请改成强密码】 echo "root:dev123456" | chpasswd # 【自定义:可选,创建非 root 开发用户】 useradd -m -s /bin/bash cppdev echo "cppdev:dev123456" | chpasswd '10.3 重建容器并映射 SSH 端口
容器创建时未映射端口,需先提交镜像,再重建容器加端口映射。
# 1) 把当前配置提交为新镜像dockercommit fedora44-cpp-dev fedora44-cpp-dev:ssh# 2) 删除旧容器dockerrm-ffedora44-cpp-dev# 3) 重建容器:映射 宿主机2222 → 容器22,sshd 作为主进程# 【自定义:宿主机端口 2222、挂载目录】dockerrun-d\--namefedora44-cpp-dev\-hcpp-dev\-p2222:22\-v~/cpp-projects:/workspace\fedora44-cpp-dev:ssh\/usr/sbin/sshd-Ddockerps--filtername=fedora44-cpp-dev💡
sshd -D作为主进程(PID 1):容器启动即自动运行 SSH,重启容器后无需手动开启。
10.4 配置 SSH 密钥免密登录(推荐)
# 1) 宿主机生成密钥(若已存在可跳过)ssh-keygen-ted25519-N""-f~/.ssh/id_ed25519# 2) 把公钥放入容器PUBKEY=$(cat~/.ssh/id_ed25519.pub)dockerexecfedora44-cpp-devsh-c" mkdir -p /root/.ssh echo '$PUBKEY' >> /root/.ssh/authorized_keys chmod 700 /root/.ssh && chmod 600 /root/.ssh/authorized_keys # 【自定义:如创建了 cppdev 用户,同样配置】 mkdir -p /home/cppdev/.ssh cp /root/.ssh/authorized_keys /home/cppdev/.ssh/ chown -R cppdev:cppdev /home/cppdev/.ssh chmod 700 /home/cppdev/.ssh && chmod 600 /home/cppdev/.ssh/authorized_keys "10.5 验证 SSH 连接
# 本机连接测试ssh-p2222root@localhost'g++ --version | head -1'# 局域网/远程机器连接# 【自定义:将 IP 换成宿主机实际 IP】ssh-p2222root@172.16.29.12410.6 提交配置(关键步骤!)
# 把 SSH 配置/密钥持久化到镜像,之后重建容器不丢失dockercommit fedora44-cpp-dev fedora44-cpp-dev:ssh11. 附录B(可选附加):定制 gdb 版本(源码编译)
背景:某些课程/工具对 gdb 版本有硬性要求(例:要求
7.8.x - 17.1.x),而 Fedora 44 自带 gdb 17.2 可能超出上限。
本节以"降到 17.1"为例,演示源码编译安装到/usr/local覆盖系统版本的通用方法。无版本要求时请跳过本节。
11.1 方案决策
| 方案 | 结论 |
|---|---|
| 换 Fedora 43 镜像 | ❌ 实测其 updates 仓库也已升级到 17.2,无效 |
| 安装旧版本 rpm(降级) | ⚠️ 可能触发依赖冲突(gdb 拆分为多个子包) |
| 源码编译指定版本 | ✅ 版本精确可控、无依赖冲突,推荐 |
💡 本实例中 rpm 降级确实触发了依赖冲突(
gdb-17.1-4.fc43缺少配套gdb-headless子包),因此切换源码编译。
11.2 下载源码
# 【自定义:按需求改版本号 17.1 → 如 17.1 / 16.2 / 15.2】# 先下载到宿主机,再拷贝进容器(容器内直连 sourceware 可能 SSL 失败)curl-sfL-o/tmp/gdb-17.1.tar.xz https://ftp.gnu.org/gnu/gdb/gdb-17.1.tar.xzdockercp/tmp/gdb-17.1.tar.xz fedora44-cpp-dev:/tmp/💡 备选镜像源(国内):
https://mirrors.tuna.tsinghua.edu.cn/gnu/gdb/gdb-17.1.tar.xz
11.3 安装编译依赖
dockerexecfedora44-cpp-dev dnfinstall-y\gcc gcc-c++maketexinfo flex bison\gmp-devel mpfr-devel libmpc-devel\readline-devel ncurses-devel python3-devel zlib-devel11.4 配置、编译、安装
dockerexecfedora44-cpp-devsh-c' cd /tmp && tar xf gdb-17.1.tar.xz && cd gdb-17.1 && mkdir -p build && cd build # 安装到 /usr/local(PATH 中优先于 /usr/bin,从而覆盖系统 gdb) ../configure \ --prefix=/usr/local \ --with-python=/usr/bin/python3 \ --enable-tui \ --disable-werror make -j$(nproc) # 多核并行编译 make install '11.5 验证
dockerexecfedora44-cpp-devsh-c' hash -r command -v gdb # 应显示 /usr/local/bin/gdb gdb --version | head -1 # 应显示 GNU gdb (GDB) 17.1 '💡重要:安装到
/usr/local后,该目录在 PATH 中排在/usr/bin之前,gdb命令自动使用新版;系统原有 gdb 保留在后备,互不冲突。
11.6 自定义空间
- 【自定义:要装其他版本】只需改 11.2 的版本号和下载 URL。
- 【自定义:若编译时想启用 LZMA(消除
.gnu_debugdata警告)】:dockerexecfedora44-cpp-dev dnfinstall-ylzma-devel# 然后重新执行 11.4 的 configure/make/make install
12. 结语:教程完结后的清理
本教程的所有容器都是"可丢弃"的:环境真正保存在镜像里(已 commit 的
fedora44-cpp-dev:gdb171及导出的 tar),容器随时可以从镜像重建。
12.1 停用容器(保留容器,可随时再启动)
dockerstop fedora44-cpp-dev# 需要时:docker start fedora44-cpp-dev12.2 删除容器(彻底清理,仅保留镜像)
# 确认镜像已提交(见第 5 章),然后删除容器dockercommit fedora44-cpp-dev fedora44-cpp-dev:gdb171# 保险起见再提交一次dockerrm-ffedora44-cpp-dev12.3 随时重建容器(从镜像)
# 【自定义:容器名 / 端口 / 挂载目录 / 镜像标签】dockerrun-d\--namefedora44-cpp-dev\-hcpp-dev\-v~/cpp-projects:/workspace\fedora44-cpp-dev:gdb171\sleepinfinity12.4 完整清理(含镜像,谨慎)
# 连镜像也删除(此时只能从导出的 tar 恢复)dockerrmi fedora44-cpp-dev:gdb171# 需要时:docker load -i ~/Desktop/fedora44-cpp-dev.tar💡建议:教程结束 → 导出镜像 tar 到桌面 → 删除容器 → 保留 tar 即等于保留了整个开发环境。
教程完。所有命令均在本环境实测通过;【自定义:...】标记处请按需调整。