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?
answer
- debuggee opens, console attaches
- UNIX domain socket by default
- --port, RUBY_DEBUG_HOST 127.0.0.1
- traffic is not encrypted
- quit detaches, kill ends the debuggee
basics
~20 sStart 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# 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 runninggo deeper
Know that a program without a terminal can still be debugged by opening it with rdbg --open and attaching with rdbg -A.
Explain the UNIX socket default, the TCP options and the difference between quit and kill in a remote session.
Treat the debug port as code execution: keep it local or tunnelled, use cookies, avoid stopping hot paths, and remove it after the investigation.
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.