skip to content

A Ruby service runs in a container without a TTY; how do you debug it with the debug gem's rdbg --open and rdbg -A, and what are the risks?

level: seniorimportance: should knowfreq 24%

answer

  1. debuggee opens, console attaches
  2. UNIX domain socket by default
  3. --port, RUBY_DEBUG_HOST 127.0.0.1
  4. traffic is not encrypted
  5. quit detaches, kill ends the debuggee

basics

~20 s

Start the program as a debuggee with rdbg --open (or require "debug/open"), then connect a console with rdbg -A. It listens on a UNIX socket by default or TCP with --port; the channel is unencrypted and grants code execution, so keep it local.

solid answer

~40 s

`binding.break` needs a terminal, which daemons and containers lack, so the debug gem splits the roles: the **debuggee** opens a port and the **console** attaches. `rdbg --open app.rb` (`-O`) starts the program, prints the socket it listens on and stops at the start; `--nonstop` or `require "debug/open_nonstop"` keeps it running. By default it uses a **UNIX domain socket** under a per-user directory; `rdbg -A` attaches to it, or `rdbg -A 12345` to a TCP port opened with `--port 12345` or `RUBY_DEBUG_PORT`. TCP binds `RUBY_DEBUG_HOST`, default `127.0.0.1`. In the remote console, `quit` only detaches while `kill` ends the debuggee. The channel is **not encrypted** and anyone who connects can run arbitrary Ruby, so reach it through `docker exec` or an SSH tunnel rather than binding a public interface.

code

bash · 8 lines
bash
# inside the container: open, keep running, UNIX socket
rdbg --open --nonstop -c -- bundle exec ruby tax_service.rb

# from the host, into the same container
docker exec -it tax-svc rdbg -A
(rdbg:remote) break TaxCalculator#tax_on if: country == "UK"
(rdbg:remote) continue
(rdbg:remote) quit        # detaches; the service keeps running

go deeper

for a junior

Know that a program without a terminal can still be debugged by opening it with rdbg --open and attaching with rdbg -A.

for a middle

Explain the UNIX socket default, the TCP options and the difference between quit and kill in a remote session.

for a senior

Treat the debug port as code execution: keep it local or tunnelled, use cookies, avoid stopping hot paths, and remove it after the investigation.

for a principal

Set policy on whether live debugging is ever allowed against shared or production environments, and what replaces it.

## Why remote debugging exists A breakpoint prompt needs a terminal wired to the program's standard input. That is missing when the program: - runs in a container with no TTY; - runs as a daemon or under a process supervisor; - uses its standard input or output as pipes. The debug gem's answer is to split the **debuggee** (the program being debugged) from the **debugger console** (your prompt) and connect them over a socket. ## Opening the debuggee | Stop at start? | `rdbg` option | In code | API | |---|---|---|---| | Yes | `rdbg --open app.rb` (`-O`) | `require "debug/open"` | `DEBUGGER__.open` | | No | `rdbg --open --nonstop app.rb` | `require "debug/open_nonstop"` | `DEBUGGER__.open(nonstop: true)` | The environment variable `RUBY_DEBUG_OPEN` has the same effect as `--open`. When the debuggee starts it prints `DEBUGGER: Debugger can attach via UNIX domain socket (...)` or the TCP address, and with stop-at-start it waits for a connection. ## Transport: UNIX socket or TCP - **Default: a UNIX domain socket**, in a per-user directory chosen from `RUBY_DEBUG_SOCK_DIR`, `XDG_RUNTIME_DIR`, a private temporary directory or `~/.rdbg-sock`. Only processes on the same machine that can reach that path can connect. - **TCP**: `--port 12345` (or `RUBY_DEBUG_PORT`), with `--port-range` to try successive ports. The listening host is `--host` / `RUBY_DEBUG_HOST`, defaulting to **`127.0.0.1`**. - Optional **`--cookie`** / `RUBY_DEBUG_COOKIE` requires the attaching console to present the same value. ## Attaching - `rdbg -A` — connect to the only UNIX socket in the default directory, or list the candidates if several processes wait. - `rdbg -A /path/to/socket`, `rdbg -A 12345`, `rdbg -A host 12345`. The prompt becomes `(rdbg:remote)` and every normal command works: `break`, `catch`, `bt`, `info`, `continue`. Two commands differ from a local session: 1. **`quit`** closes only the console; the debuggee keeps running and you can attach again later. 2. **`kill`** terminates the debuggee with `Kernel#exit!`. ## The risks A remote debugger is a remote code execution endpoint by design: anything typed at the console is evaluated inside the process. - **No encryption.** The gem states that messages between debugger and debuggee are not encrypted. - **No authentication beyond an optional cookie.** Binding `RUBY_DEBUG_HOST=0.0.0.0` in a container and publishing the port exposes a shell into your application. - **Pausing stops real work.** A breakpoint in a request path blocks that thread; in a live service, others queue behind it. - **`open_nonstop` as a permanent backdoor** is explicitly flagged by the gem as dangerous. Safer patterns: - keep the UNIX socket and attach from inside the container with `docker exec -it <container> rdbg -A`; - if TCP is unavoidable, bind `127.0.0.1` and reach it through an SSH tunnel or port-forward, never a public listener; - set a cookie, enable it only for the investigation, and redeploy without it afterwards; - prefer `do:` log-point breakpoints over stopping in hot paths. ## Editors use the same mechanism `rdbg --open=vscode` or `--open=chrome` prepares the debuggee for those front ends; they speak to the same port instead of a terminal console.

  • You attach with rdbg -A, finish investigating and type quit. Is the service still running?
    Yes. In a remote session `quit` closes only the console; the debuggee continues and you can attach again. To stop the debuggee itself, use `kill`, which ends it with `Kernel#exit!`.
  • Why not simply set RUBY_DEBUG_HOST=0.0.0.0 and publish the port?
    The debugger channel is unencrypted and anyone who connects can evaluate arbitrary Ruby inside the process, reading secrets or changing data. Binding every interface turns a debugging aid into a remote shell. Keep the UNIX socket, or bind 127.0.0.1 and reach it through an SSH tunnel, with a cookie set.
  • Why can a breakpoint in a remote production-like service cause an outage?
    Stopping pauses the thread that hit the breakpoint while you type. In a server that thread was serving a request; others queue behind it or time out. Prefer `do:` breakpoints that print and continue, narrow conditions, and short sessions.

saying these in an interview costs you the question

  • rdbg --open listens on 0.0.0.0 TCP by default.
  • The debug gem encrypts traffic between console and debuggee.
  • quit in a remote rdbg session always kills the debuggee.
  • A debug port is harmless because it only allows reading state.
  • Remote debugging needs a TTY inside the container.