Skip to main content

Remote Debugging CI/CD Pipelines

· 5 min read

One of the key features of NUKE is that you can run and debug builds locally as opposed to repeatedly triggering CI/CD pipeline with a lot of logging all over the place. Based on broad feedback, this solves more than 90% of the cases where YAML and other schemas are difficult to troubleshoot. While the vast majority of developers has yet to experience this advantage, we will try to go a step further.

In practice, differences between your local and CI/CD environment are unavoidable. Even with dockerized environments, you can't rule out misconfiguration of ports, volumes, or services. Beyond build automation, many of us have also faced flaky tests and race conditions that never occurred locally. Overall, problems in CI/CD pipelines are very tedious and time-consuming to investigate.

In this post, we will take a look at a proof-of-concept that allows you to attach the debugger to any remote process running in your CI/CD environment. Our tools of choice are JetBrains Rider and GitHub Actions, but the principles work much the same with any other tools.

Preparing for Remote Debugging

Enabling remote debugging is as simple as adding an attribute to your Build class:

[EnableRemoteDebugging(WaitForDebugger = true)]
class Build : NukeBuild
{
Target Compile => _ => _
.Executes(() => { /* ... */ });
}

Once the build is running in your CI/CD environment, it will print detailed connection instructions to the log output:

[INF] SSH tunnel opened: ssh -t matkoch.nuke.build
[INF] Fingerprint: MD5:74:8b:e4:12:3b:b0:db:1e:88:4c:22:fd:ee:d9:b3:40
[INF] SSH Configuration:

Host matkoch.nuke.build
...

Before you can connect, you need to go through a one-time SSH setup. This includes updating your .ssh/config as above and creating a new SSH session configuration in JetBrains Rider:

Attaching to a Remote Process

After completing the initial setup, you can connect to the remote host:

The SSH service on the remote machine will verify your identity using your public key, which can be obtained from GitHub or from a database. That makes sure that only authorized users can connect.

From the list of .NET processes, you can select any process to attach the debugger. In this example, we will attach to the build process, but it could also be a dotnet test process:

Debugging works exactly the same on the remote host as on your local machine. You can step through your code, inspect variables, and even execute commands in the immediate window:

The logging call from above will immediately execute in the GitHub Actions runner:

Creating Terminal Sessions

Beyond debugging .NET processes, you can also spin up simple SSH terminal session:

ssh -t matkoch.nuke.build

This comes in handy when you need to verify the file structure, check on the content of specific files, or just explore your CI/CD environment:

Conveniently, you'll also switch directly to the root directory once you join the terminal session.

The community has built projects like upterm and tmate, which have been very inspiring. However, both of these are limited to just terminal sessions and don't allow to attach to any process you'd wish for. In addition, transmission is done in plaintext through a relay server, which means that the public relay server can be a security concern. Alternatively, you can also self-host an instance.

Conclusion

Everyone remembers at least one of those WTF moments, when absolutely nothing that happened in the CI/CD pipeline made any sense. How much time, money, and nerve did it take? Once more, NUKE pushes the limits of what's possible in build automation. With a simple attribute, you can enable remote debugging and attach to any process or just spin up a terminal session for your investigations.

What was your latest WTF moment? Would this feature have helped? Make sure to let us know in the comments!