quaywin

quaywin

Caudata - A multi-server TUI log streamer using ex_ratatui, Burrito, and native :ssh

Hey everyone,

I wanted to share a tool I’ve been working on called Caudata :lizard:.

Quick Install: curl -fsSL https://raw.githubusercontent.com/quaywin/caudata/main/install.sh | sh

Caudata TUI Demo

Why I built this

My main frustration was tailing Docker and system logs across multiple remote servers. I always ended up opening a bunch of terminal tabs, SSH-ing into each box, and running docker logs -f manually.

Web-based dashboards like Portainer or Dozzle are great, but they require installing agents, setting up web servers, or exposing Docker sockets on the remote hosts, which I wanted to avoid for security and maintenance reasons.

Caudata is a local TUI tool that reads your existing ~/.ssh/config profiles, logs into your servers, auto-discovers running Docker containers/systemd services, and streams their logs into a single terminal window. Zero configuration or agents required on the target servers.

What it does

  • Flexible Authentication: It parses your ~/.ssh/config file to connect automatically using your private keys. If you need to connect to a new host manually, it also supports adding connections with custom hostname, username, and password credentials securely via an in-app modal.
  • Auto-discovery & reconnects: Automatically detects running Docker containers (and systemd services) and reconnects the log streams if a container restarts or rebuilds.
  • TUI visual copy: You can press v to enter visual select mode in the log pane and copy lines directly to your system clipboard with y.
  • Server metrics: Displays basic CPU, RAM, and Disk usage for each server on the sidebar.
  • Single-binary distribution: Packaged with Burrito, so users can download and run it without needing Elixir or Erlang installed on their machine.

Some technical notes on the Elixir/OTP stack

Using Elixir for a CLI/TUI utility was a great experience, and a few things really stood out:

  1. Erlang’s :ssh module is a hidden gem: I didn’t need to wrap any SSH CLI binaries or pull in heavy dependencies. Erlang’s native :ssh and :ssh_sftp handle secure connections and stream multiplexing perfectly. Each server connection maps nicely to a supervised GenServer.
  2. ex_ratatui: TUI rendering is powered by @mcass19’s ex_ratatui (Rust’s Ratatui bindings via Rustler). It brings LiveView-like ergonomics to terminal rendering.
  3. Web rendering in dev mode: During local development, I used phoenix_ex_ratatui to render the TUI directly in the browser via LiveView. It made debugging UI layouts and sizing much faster.
  4. Self-upgrades with Burrito: The release is bundled into a single file. We also wrote a quick caudata upgrade command that checks GitHub releases, downloads the target-specific binary, and swaps it in-place.

The project is still in its early stages. I’d love to hear your feedback on the OTP architecture, codebase structure, or the TUI UX.

Check out the repo here:

Most Liked

dimitarvp

dimitarvp

Oh wow, this is great. I’ll use it.

Where Next?

Popular in Announcing Top

asiniy
Hey there! I wrote a download elixir package which does exactly what its name about - an easy way to download files. I saw solutions ab...
New
seancribbs
Today I released a new dialyzer Mix task as the dialyzex package! At the time we started writing this task, the existing dialyzer integra...
New
tmbb
I’ve been working on two packages (not on hex.pm yet) to build admin interfaces for phoenix apps: bureaucrat - which contains a bunch ...
New
MRdotB
I needed to reuse React components from my Chrome extension in my Phoenix/LiveView backend. I noticed that for Svelte/Vue, there are live...
New
RobertDober
Earmark is a pure-Elixir Markdown converter. It is intended to be used as a library (just call Earmark.as_html), but can also be used as...
239 12645 134
New
Flo0807
Hello everyone! I am excited to share our heart project Backpex with you. After building several Phoenix applications, we realized that...
New
hpopp
After just over two years in development, this latest version of Pigeon is what I finally consider done in regards to my original vision ...
New
benlime
LiveMotion enables high performance animations declared on the server and run on the client. As a follow up to my previous thread A libr...
New
marcuslankenau
I feel kind of stuck with the absence of a proper xml library for Elixir. Currently I use SweetXML which was ok for me more or less to pa...
New
tmbb
I’ve decided to create this topic to discuss optimization possibilities for something like Phoenix LiveView. I’ve created this topic unde...
144 10462 141
New

Other popular topics Top

axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 48342 226
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
New
Nvim
Anybody knows a comprehensive comparison of Django and Phoenix, thanks for the help. Where are they similar? Where do they differ the m...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
klo
Got a question about when to concat vs. prepending items to list then reversing to achieve appending. So i know lists boil down to [1 | ...
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => ...
New

Latest on Elixir Forum

We're in Beta

About us Mission Statement