# Building a C++ App in Jenkins (Docker Agent) Step-by-step guide to set up a Jenkins pipeline that builds a C++ project using a Docker container as the build agent (DooD). ## 1. Push your C++ project to the git server On the server, create a bare repo: ```bash cd /var/git/repos git init --bare my-cpp-app.git chown -R 1000:1000 my-cpp-app.git ``` From your local machine, push your code: ```bash cd /path/to/my-cpp-app git init git remote add origin ssh://git@/repos/my-cpp-app.git git add . git commit -m "Initial commit" git push -u origin master ``` ## 2. Add a Jenkinsfile to your project Create a file called `Jenkinsfile` in the root of your C++ project. ### Simple build (g++ directly) ```groovy pipeline { agent { docker { image 'gcc:latest' } } stages { stage('Build') { steps { sh 'g++ -o my_app main.cpp' } } stage('Test') { steps { sh './my_app' } } } } ``` Adjust the `g++` command to match your source files. ### CMake build ```groovy pipeline { agent { docker { image 'gcc:latest' } } stages { stage('Configure') { steps { sh 'cmake -B build -S .' } } stage('Build') { steps { sh 'cmake --build build' } } stage('Test') { steps { sh 'cd build && ctest --output-on-failure' } } } } ``` > Note: The `gcc:latest` image includes `g++`, `gcc`, `make`, and `cmake`. > If you need extra libraries (e.g., Boost), you can use a custom image or > add `apt-get install` steps. Commit and push the Jenkinsfile: ```bash git add Jenkinsfile git commit -m "Add Jenkinsfile for CI" git push ``` ## 3. Create the Jenkins pipeline job 1. Open Jenkins at `https://jenkins.swave.lol` (or `https://swave.lol/jenkins`) 2. Click **New Item** (top-left) 3. Enter a name, e.g. `my-cpp-app` 4. Select **Pipeline**, then click **OK** ## 4. Configure the pipeline source On the job configuration page: 1. Scroll down to the **Pipeline** section 2. Change **Definition** from "Pipeline script" to **Pipeline script from SCM** 3. Set **SCM** to **Git** 4. In **Repository URL**, enter: `git://git-server/my-cpp-app.git` - This works because Jenkins and git-server are on the same `jenkins_network` (172.23.0.0/16) - `git-server` resolves to `172.23.0.3` via Docker DNS - The git daemon protocol (`git://`) on port 9418 requires no credentials - Alternative: `ssh://git@git-server/repos/my-cpp-app.git` (requires SSH key setup in Jenkins) 5. Set **Branch Specifier** to `*/master` (or `*/main`, whatever your default branch is) 6. **Script Path**: leave as `Jenkinsfile` (default) 7. Click **Save** ### Large repositories For large repos (e.g., the Linux kernel), the default 10-minute fetch timeout is too short. In the SCM config, click **Additional Behaviours** > **Advanced clone behaviours** and set: - **Fetch tags**: uncheck - **Timeout (in minutes) for clone and fetch**: `120` (or more) - **Shallow clone**: check - **Shallow clone depth**: `1` Shallow clone fetches only the latest commit, reducing clone size significantly. ### Automatic SCM checkout When using **Pipeline script from SCM**, Jenkins automatically clones the repository before running the Jenkinsfile (the "Declarative: Checkout SCM" step). The workspace already contains the code, so you do **not** need a `checkout` or `git clone` stage in your Jenkinsfile — adding one would clone the repo a second time. Just use the files directly in your build stages: ```groovy pipeline { agent { docker { image 'gcc:latest' } } stages { stage('Build') { steps { // Source code is already in the workspace from SCM checkout sh 'g++ -o my_app main.cpp' } } } } ``` ## 5. Run the build 1. On the job page, click **Build Now** (left sidebar) 2. A build number will appear under **Build History** — click it 3. Click **Console Output** to watch the build live You will see Jenkins: - Pull the `gcc:latest` Docker image (first time only) - Spin up a temporary container on the host (DooD via `/var/run/docker.sock`) - Clone your repo inside the container - Run each stage (`Build`, `Test`) - Destroy the container when done **Important:** The build container is ephemeral — everything inside it (including compiled binaries) is destroyed when the pipeline finishes. To keep build artifacts, see the next section. ## 6. Saving build artifacts By default the Docker build container is destroyed after the pipeline runs, taking all compiled binaries with it. Use `archiveArtifacts` to copy files into Jenkins' permanent storage before the container is removed. ### Simple build with artifacts ```groovy pipeline { agent { docker { image 'gcc:latest' } } stages { stage('Build') { steps { sh 'g++ -o my_app main.cpp' } } stage('Test') { steps { sh './my_app' } } stage('Archive') { steps { archiveArtifacts artifacts: 'my_app', fingerprint: true } } } } ``` ### CMake build with artifacts ```groovy pipeline { agent { docker { image 'gcc:latest' } } stages { stage('Configure') { steps { sh 'cmake -B build -S .' } } stage('Build') { steps { sh 'cmake --build build' } } stage('Test') { steps { sh 'cd build && ctest --output-on-failure' } } stage('Archive') { steps { archiveArtifacts artifacts: 'build/my_app', fingerprint: true } } } } ``` Archived files are stored in `/var/jenkins_home/jobs//builds//archive/` and can be downloaded from the build page in the Jenkins web UI under **Build Artifacts**. You can use glob patterns to archive multiple files, e.g.: - `archiveArtifacts artifacts: 'build/**/*.so'` — all shared libraries - `archiveArtifacts artifacts: 'build/bin/*'` — everything in a bin directory ## 7. Automatic builds (optional) ### Option A — Poll SCM 1. Go to job config (**Configure**) 2. Under **Build Triggers**, check **Poll SCM** 3. Set schedule, e.g. `H/5 * * * *` (check every 5 minutes) 4. Click **Save** ### Option B — Git post-receive hook Create a hook that notifies Jenkins immediately on push: ```bash cat > /var/git/repos/my-cpp-app.git/hooks/post-receive << 'HOOK' #!/bin/bash curl -s "http://jenkins:8080/jenkins/git/notifyCommit?url=git://git-server/my-cpp-app.git" > /dev/null 2>&1 & HOOK chmod +x /var/git/repos/my-cpp-app.git/hooks/post-receive ``` This requires the **Git plugin** and **Poll SCM** to be enabled in the job config (the schedule can be empty, e.g. just `H * * * *`). ## Incremental builds and cleaning the workspace After the first successful build, Jenkins keeps the workspace directory (`/var/jenkins_home/jobs//workspace/`) between builds. On subsequent runs it does a `git fetch` + `git checkout` instead of a full clone, only downloading new commits. This makes builds much faster, especially for large repositories like the Linux kernel. The same applies to Docker images: Jenkins pulls the image (e.g., `kernel-builder` or `gcc:latest`) once and caches it locally. Future builds reuse the cached image. However, the Docker **container** is always created fresh for each build and destroyed when done — only the workspace persists. Sometimes you need a completely clean build (e.g., to rule out stale object files causing issues). Pipeline jobs do not have a "Wipe Out Workspace" button in the UI (only Freestyle jobs do). There are two ways to clean it: ### Option A — Delete from host ```bash rm -rf /var/jenkins_home/jobs//workspace ``` The next build will do a fresh clone. ### Option B — Add cleanWs() to Jenkinsfile Requires the **Workspace Cleanup** plugin. Add a `post` block to automatically wipe the workspace after every build: ```groovy pipeline { agent { docker { image 'gcc:latest' } } stages { stage('Build') { steps { sh 'g++ -o my_app main.cpp' } } } post { always { cleanWs() } } } ``` Note: using `cleanWs()` means every build will do a full clone, which is slow for large repos. Use it only when you actually need clean builds. ## How DooD works Jenkins is configured with Docker-out-of-Docker: it mounts the host's `/var/run/docker.sock` into the Jenkins container. When the pipeline runs `docker { image 'gcc:latest' }`, Jenkins creates a **sibling container** on the host (not nested), runs the build steps inside it, and removes it after. The Jenkins container never needs compilers installed — everything runs in the ephemeral `gcc` container.