<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Luis Gustavo S. Barreto</title>
    <description>The latest articles on DEV Community by Luis Gustavo S. Barreto (@gustavosbarreto).</description>
    <link>https://dev.to/gustavosbarreto</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F906272%2Fab50c065-46a0-4b79-9819-ff27606bd5c2.jpeg</url>
      <title>DEV Community: Luis Gustavo S. Barreto</title>
      <link>https://dev.to/gustavosbarreto</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gustavosbarreto"/>
    <language>en</language>
    <item>
      <title>Remote AI Coding with Claude Code and ShellHub</title>
      <dc:creator>Luis Gustavo S. Barreto</dc:creator>
      <pubDate>Fri, 10 Oct 2025 15:37:07 +0000</pubDate>
      <link>https://dev.to/gustavosbarreto/remote-ai-coding-with-claude-code-and-shellhub-25</link>
      <guid>https://dev.to/gustavosbarreto/remote-ai-coding-with-claude-code-and-shellhub-25</guid>
      <description>&lt;p&gt;In the rapidly evolving world of AI-assisted development, having access to powerful AI coding assistants from anywhere has become essential.&lt;/p&gt;

&lt;p&gt;I've been a big fan of Claude Code since it launched. Having an AI assistant that can understand large codebases, make thoughtful edits, and actually reason about architecture has been game-changing for my workflow.&lt;/p&gt;

&lt;p&gt;But what if you want to access Claude Code remotely, from any device?&lt;/p&gt;

&lt;p&gt;When I started a conversation on my laptop, that context stayed there. If I wanted to continue from my desktop, I'd have to re-explain everything. And if I was on my tablet or phone? Forget about it. I needed a way to have my Claude Code environment accessible from anywhere, without losing context or messing with complex networking.&lt;/p&gt;

&lt;p&gt;That's when I realized: what if I just containerized Claude Code and made it accessible via SSH through ShellHub?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Here's the thing—Claude Code runs locally on your machine. That's great for privacy and speed, but terrible for mobility.&lt;/p&gt;

&lt;p&gt;I could have set up a VM in the cloud, but then I'm paying for compute even when I'm not using it. Plus, I wanted to keep my development close to home—sometimes I need access to local hardware, Docker on my machine, or files that shouldn't leave my network.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: ShellHub + Docker
&lt;/h2&gt;

&lt;p&gt;ShellHub is this cool open-source project that makes SSH access dead simple. No port forwarding, no VPN setup, no dynamic DNS headaches. Devices register with a ShellHub server and become instantly accessible via SSH from anywhere. It's like Tailscale, but specifically designed for SSH access, and you can self-host the server.&lt;/p&gt;

&lt;p&gt;The idea is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run Claude Code in a Docker container&lt;/li&gt;
&lt;li&gt;Install ShellHub agent in the container&lt;/li&gt;
&lt;li&gt;SSH in from any device and land directly in Claude Code&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And since everything runs in Docker, the workspace and Claude's context persist. Start a session on your laptop, continue from your phone using Termius—it all just works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;The setup is surprisingly straightforward. Here's what we're building:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Base&lt;/strong&gt;: Alpine Linux (small, fast)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code&lt;/strong&gt;: Installed via npm&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ShellHub Agent&lt;/strong&gt;: Downloaded from GitHub releases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User shell&lt;/strong&gt;: Bash configured to auto-launch Claude Code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workspace&lt;/strong&gt;: Persistent directory for your code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The clever bit is in how we handle the shell. When you SSH in, ShellHub runs &lt;code&gt;bash --login&lt;/code&gt;. We configure the &lt;code&gt;~/.profile&lt;/code&gt; to automatically &lt;code&gt;exec&lt;/code&gt; into Claude Code for interactive SSH sessions.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SSH in → bash loads → &lt;code&gt;.profile&lt;/code&gt; runs → Claude Code launches&lt;/li&gt;
&lt;li&gt;You land directly in Claude Code without typing any commands&lt;/li&gt;
&lt;li&gt;Non-interactive commands (like &lt;code&gt;scp&lt;/code&gt;) work normally&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's build it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Dockerfile
&lt;/h3&gt;

&lt;p&gt;I wanted to keep this lean. Alpine Linux as the base gives us a tiny image (~200MB total), and we only install what we need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; alpine:3.19&lt;/span&gt;

&lt;span class="c"&gt;# Install dependencies&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    nodejs &lt;span class="se"&gt;\
&lt;/span&gt;    npm &lt;span class="se"&gt;\
&lt;/span&gt;    bash &lt;span class="se"&gt;\
&lt;/span&gt;    curl &lt;span class="se"&gt;\
&lt;/span&gt;    ca-certificates &lt;span class="se"&gt;\
&lt;/span&gt;    git &lt;span class="se"&gt;\
&lt;/span&gt;    openssh-client &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;sudo&lt;/span&gt;

&lt;span class="c"&gt;# Install Claude Code CLI&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @anthropic-ai/claude-code

&lt;span class="c"&gt;# Download and install ShellHub agent binary&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;-L&lt;/span&gt; https://github.com/shellhub-io/shellhub/releases/download/v0.21.0-rc.2/shellhub-agent-linux-amd64.gz &lt;span class="nt"&gt;-o&lt;/span&gt; /tmp/shellhub-agent.gz &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;gunzip&lt;/span&gt; /tmp/shellhub-agent.gz &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;mv&lt;/span&gt; /tmp/shellhub-agent /usr/local/bin/shellhub-agent &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;chmod&lt;/span&gt; +x /usr/local/bin/shellhub-agent

&lt;span class="c"&gt;# Create claudeuser with bash as shell and home directory&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;adduser &lt;span class="nt"&gt;-D&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /bin/bash &lt;span class="nt"&gt;-h&lt;/span&gt; /home/claudeuser claudeuser &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"claudeuser ALL=(ALL) NOPASSWD:ALL"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/sudoers

&lt;span class="c"&gt;# Create workspace directory inside user home&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /home/claudeuser/workspace &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; claudeuser:claudeuser /home/claudeuser

&lt;span class="c"&gt;# Copy profile configuration&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; profile /home/claudeuser/.profile&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown &lt;/span&gt;claudeuser:claudeuser /home/claudeuser/.profile

&lt;span class="c"&gt;# Copy entrypoint script&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; entrypoint.sh /entrypoint.sh&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /entrypoint.sh

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/entrypoint.sh"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few notes on design choices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I download the ShellHub agent binary directly from GitHub releases&lt;/li&gt;
&lt;li&gt;The user gets &lt;code&gt;/bin/bash&lt;/code&gt; as their shell in &lt;code&gt;/etc/passwd&lt;/code&gt;. I initially tried setting the shell to &lt;code&gt;/usr/local/bin/claude&lt;/code&gt; directly, but ShellHub's &lt;code&gt;--login&lt;/code&gt; flag doesn't play nice with non-standard shells&lt;/li&gt;
&lt;li&gt;Workspace lives in the user's home directory (&lt;code&gt;/home/claudeuser/workspace&lt;/code&gt;), which makes permissions straightforward&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Profile Magic
&lt;/h3&gt;

&lt;p&gt;Here's where it gets interesting. The &lt;code&gt;.profile&lt;/code&gt; file is what actually launches Claude:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="c"&gt;# Set working directory to workspace&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/workspace 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ~

&lt;span class="c"&gt;# Check if this is an interactive shell&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$-&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;i&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="c"&gt;# Launch Claude Code for interactive sessions&lt;/span&gt;
    &lt;span class="nb"&gt;exec&lt;/span&gt; /usr/local/bin/claude
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;$-&lt;/code&gt; variable contains shell flags. When &lt;code&gt;i&lt;/code&gt; is present, you've got an interactive shell. The &lt;code&gt;exec&lt;/code&gt; is key—it &lt;em&gt;replaces&lt;/em&gt; the bash process with Claude Code, so when you exit Claude, the SSH session terminates cleanly.&lt;/p&gt;

&lt;p&gt;Why check for interactive? Because &lt;code&gt;scp&lt;/code&gt;, automated scripts, and other non-interactive SSH commands need to work. Without this check, trying to &lt;code&gt;scp&lt;/code&gt; a file would launch Claude Code instead. Not ideal.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Entrypoint
&lt;/h3&gt;

&lt;p&gt;The entrypoint starts the ShellHub agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="c"&gt;# Configure ShellHub agent (agent reads from env vars directly)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SHELLHUB_TENANT_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ERROR: SHELLHUB_TENANT_ID environment variable is required"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SHELLHUB_SERVER_ADDRESS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ERROR: SHELLHUB_SERVER_ADDRESS environment variable is required"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Start ShellHub agent in background (configured via env vars)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Starting ShellHub agent..."&lt;/span&gt;
/usr/local/bin/shellhub-agent &amp;amp;

&lt;span class="nv"&gt;AGENT_PID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$!&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ShellHub agent started (PID: &lt;/span&gt;&lt;span class="nv"&gt;$AGENT_PID&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Claude Code shell ready for connections"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Workspace: /home/claudeuser/workspace"&lt;/span&gt;

&lt;span class="c"&gt;# Keep container running and monitor agent&lt;/span&gt;
&lt;span class="nb"&gt;wait&lt;/span&gt; &lt;span class="nv"&gt;$AGENT_PID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running It
&lt;/h2&gt;

&lt;p&gt;Build the image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; shellhub-claude-code &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the container (adjust the values for your setup):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;SHELLHUB_TENANT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;00000000-0000-4000-0000-000000000000 &lt;span class="se"&gt;\ &lt;/span&gt; &lt;span class="c"&gt;# Your tenant ID from ShellHub&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;SHELLHUB_SERVER_ADDRESS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://172.17.0.1 &lt;span class="se"&gt;\ &lt;/span&gt;               &lt;span class="c"&gt;# Your ShellHub server address&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;SHELLHUB_PRIVATE_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/shellhub/shellhub.key &lt;span class="se"&gt;\ &lt;/span&gt;         &lt;span class="c"&gt;# Path inside container to key&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;SHELLHUB_PREFERRED_HOSTNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;claudecode &lt;span class="se"&gt;\ &lt;/span&gt;                  &lt;span class="c"&gt;# Device name in ShellHub&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; ./keys:/etc/shellhub &lt;span class="se"&gt;\ &lt;/span&gt;                                     &lt;span class="c"&gt;# Mount local keys directory&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; ./workspace:/home/claudeuser/workspace &lt;span class="se"&gt;\ &lt;/span&gt;                   &lt;span class="c"&gt;# Mount local workspace&lt;/span&gt;
  shellhub-claude-code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Replace &lt;code&gt;SHELLHUB_TENANT_ID&lt;/code&gt; with your tenant ID from the ShellHub dashboard&lt;/li&gt;
&lt;li&gt;Replace &lt;code&gt;SHELLHUB_SERVER_ADDRESS&lt;/code&gt; with your ShellHub server URL&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;./keys&lt;/code&gt; directory with your &lt;code&gt;shellhub.key&lt;/code&gt; file (if using device authentication)&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;./workspace&lt;/code&gt; directory for your code files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The container starts, the ShellHub agent registers with your server, and the device shows up in your dashboard. Accept it, and you're good to go.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connecting
&lt;/h3&gt;

&lt;p&gt;Once your device is registered and accepted in ShellHub, you'll see it in the dashboard with connection details:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4kh3ein76q32kcgtgeoj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4kh3ein76q32kcgtgeoj.png" alt="ShellHub Connection Interface" width="800" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can connect in two ways:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: Through the ShellHub UI&lt;/strong&gt;&lt;br&gt;
Click the terminal icon in the dashboard (as shown above) to open a web-based SSH session directly in your browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2: Using your SSH client&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh claudeuser@namespace.claudecode@your-shellhub-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Either way, you land directly in Claude Code. No commands to run, no setup.&lt;/p&gt;

&lt;p&gt;First time connecting? Claude Code will automatically prompt you to authenticate. It'll generate a link for you to visit—just open it, get your authentication token, and paste it back into Claude Code. That's it. From now on, you've got Claude available from anywhere.&lt;/p&gt;

&lt;p&gt;And just like that, you're in:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiscll1d64w3txg9zfn09.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiscll1d64w3txg9zfn09.png" alt="Claude Code Ready" width="800" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can start a conversation on my laptop, SSH in from my phone on Termius during my commute, and pick up exactly where I left off. The workspace persists, Claude's context persists—it all just works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Works
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;It works everywhere.&lt;/strong&gt; Your container can be behind NAT, on a residential IP, wherever. ShellHub handles the networking. No port forwarding, no dynamic DNS, no VPN.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Any device with SSH.&lt;/strong&gt; Laptop, desktop, tablet, phone. Even a friend's computer if you trust them with your SSH key. ShellHub only needs a standard SSH client.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context persists.&lt;/strong&gt; Unlike starting fresh each time, your Claude conversations and workspace state survive between sessions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's yours.&lt;/strong&gt; Self-hosted, open source, no vendor lock-in. You control where it runs and who has access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things to Know
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Single user&lt;/strong&gt;: This setup is for one person at a time. Multiple SSH sessions will fight over the same Claude instance. For teams, spin up separate containers per user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API keys&lt;/strong&gt;: Claude stores your API key in &lt;code&gt;/home/claudeuser&lt;/code&gt;. If you don't persist that directory with a volume, you'll need to &lt;code&gt;/login&lt;/code&gt; again after container restarts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Not Just Use...
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Tailscale?&lt;/strong&gt; Tailscale requires installing software on every device you want to access from. ShellHub just needs SSH—works great on shared computers or locked-down environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Direct SSH?&lt;/strong&gt; Sure, if you want to deal with port forwarding, dynamic DNS, or paying for a static IP. ShellHub makes all that disappear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This setup has become part of my daily workflow. I've got Claude accessible from my home lab, reachable from anywhere. No friction, no complexity, just works.&lt;/p&gt;

&lt;p&gt;The whole thing is maybe 100 lines of code across a Dockerfile and shell scripts. But it solves a real problem: making AI-assisted development truly portable.&lt;/p&gt;

&lt;p&gt;If you've been wanting remote access to Claude Code without cloud vendor lock-in, give this a try.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>ssh</category>
      <category>vpn</category>
    </item>
    <item>
      <title>Dominando SCM_RIGHTS: Transferência de WebSocket entre processos no Linux</title>
      <dc:creator>Luis Gustavo S. Barreto</dc:creator>
      <pubDate>Fri, 08 Aug 2025 19:21:31 +0000</pubDate>
      <link>https://dev.to/gustavosbarreto/dominando-scmrights-transferencia-de-fd-no-linux-para-alta-performance-5ch</link>
      <guid>https://dev.to/gustavosbarreto/dominando-scmrights-transferencia-de-fd-no-linux-para-alta-performance-5ch</guid>
      <description>&lt;p&gt;As instâncias do ShellHub Enterprise lidam com dezenas de milhares de conexões WebSocket concorrentes, além de múltiplas requisições por segundo nos endpoints HTTP da nossa API.&lt;/p&gt;

&lt;p&gt;Em cada instância nós temos um API proxy baseado em Nginx/Openresty que centraliza todo o tráfego HTTP do servidor, ou seja, todas as rotas públicas passam por ele, como rotas do painel de administração Web, rotas que o agent do ShellHub rodando nos dispositivos acessa, além do próprio túnel WebSocket que é estabelecido entre eles e também APIs de integração dos nossos clientes batendo em alguns endpoints.&lt;/p&gt;

&lt;p&gt;Tudo isso gera um grande consumo de recursos do servidor como processamento e memória (geralmente o mais caro). Para nos mantermos competitivos nesse mercado ultra nichado, nossa stack foi projetada meticulosamente para ser a menor possível e entregar o máximo possível sem abrir mão da excelência técnica (foco do open source).&lt;/p&gt;

&lt;p&gt;Embora o uso do nginx como proxy central traga essa simplicidade operacional, entregando um controle centralizado, gerenciamento automático de certificados HTTPS, roteamento para cada serviço da nossa stack, ele acaba se tornando um ponto crítico de acúmulo de carga.&lt;/p&gt;

&lt;p&gt;E no caso específico do WebSocket (onde nossa tecnologia se baseia), essa arquitetura implica que o nginx mantém duas conexões ativas para cada cliente: uma com o cliente final (downstream) e outra com o backend (upstream). Para cada mensagem WebSocket, há cópias entre o espaço de usuário e o kernel, buffers alocados por conexão e vários context switches. Com dezenas de milhares de conexões WebSocket ativas isso consome muitos recursos.&lt;/p&gt;

&lt;p&gt;Diante dessa problemática, gostaria de compartilhar como otimizar esse processo utilizando técnicas avançadas do próprio kernel Linux, como a passagem de file descriptors (sockets) via &lt;code&gt;SCM_RIGHTS&lt;/code&gt;, que são um tanto quanto desconhecidas por muitos, mas que podem ser muito úteis em diminuir consumo de recursos de um servidor.&lt;/p&gt;

&lt;h2&gt;
  
  
  O Problema Fundamental: Proxy como Middleman
&lt;/h2&gt;

&lt;p&gt;A arquitetura tradicional de WebSockets em produção segue um padrão bem estabelecido:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpg3xua8jugyrdnfzjeiw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpg3xua8jugyrdnfzjeiw.png" alt=" " width="800" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Neste modelo, o Nginx atua como um intermediário constante, mantendo duas conexões para cada cliente: uma upstream e uma downstream. Para cada mensagem trocada, os dados são:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Lidos do kernel para o espaço do usuário (Nginx)&lt;/li&gt;
&lt;li&gt;Processados pelo Nginx&lt;/li&gt;
&lt;li&gt;Escritos de volta ao kernel&lt;/li&gt;
&lt;li&gt;Enviados para o backend ou cliente&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Esta arquitetura, embora simples e amplamente adotada, possui limitações inerentes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Overhead de Memória&lt;/strong&gt;: O Nginx aloca buffers para cada conexão ativa&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latência Adicional&lt;/strong&gt;: Cada hop introduz latência de processamento&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CPU Overhead&lt;/strong&gt;: Context switches entre processos e cópias desnecessárias de dados&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O Nginx mantém &lt;strong&gt;dois file descriptors&lt;/strong&gt; para cada cliente: um para o cliente final e outro para o backend. Cada mensagem WebSocket precisa:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;read(5)&lt;/code&gt; - ler do cliente&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;write(6)&lt;/code&gt; - escrever para o backend&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;read(6)&lt;/code&gt; - ler resposta do backend
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;write(5)&lt;/code&gt; - escrever para o cliente&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh4zvfn389vmircvni0e0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh4zvfn389vmircvni0e0.png" alt=" " width="800" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A Solução: &lt;code&gt;SCM_RIGHTS&lt;/code&gt; e a transferência de file descriptors
&lt;/h2&gt;

&lt;p&gt;O &lt;code&gt;SCM_RIGHTS&lt;/code&gt; é um mecanismo do kernel Linux que permite a transferência de file descriptors abertos entre processos através de Unix Domain Sockets. Esta funcionalidade, presente nos sistemas Unix desde os anos 90, oferece uma capacidade única: &lt;strong&gt;transferir a propriedade de uma conexão de rede de um processo para outro&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  O que é um File Descriptor?
&lt;/h3&gt;

&lt;p&gt;Um file descriptor (FD) é simplesmente um número inteiro que o kernel usa como "índice" para identificar arquivos, sockets de rede, pipes ou qualquer recurso de I/O aberto por um processo. É como um "ID único" que o processo usa para se referir a uma conexão específica.&lt;/p&gt;

&lt;p&gt;Por exemplo: quando seu processo abre uma conexão TCP, o kernel retorna o número &lt;code&gt;5&lt;/code&gt;. A partir daí, sempre que você quiser ler/escrever nessa conexão, você usa &lt;code&gt;read(5)&lt;/code&gt; ou &lt;code&gt;write(5)&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Como Funciona no Kernel
&lt;/h3&gt;

&lt;p&gt;Quando um processo envia um file descriptor via &lt;code&gt;SCM_RIGHTS&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;O kernel não transfere apenas o número do descriptor&lt;/li&gt;
&lt;li&gt;Ele cria uma nova entrada na tabela de FDs do processo receptor&lt;/li&gt;
&lt;li&gt;Ambas as entradas apontam para a mesma estrutura de arquivo interno&lt;/li&gt;
&lt;li&gt;O contador de referência é incrementado&lt;/li&gt;
&lt;li&gt;O processo receptor pode usar o FD como se tivesse criado a conexão originalmente&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Esta operação é &lt;strong&gt;atômica&lt;/strong&gt; e &lt;strong&gt;zero-copy&lt;/strong&gt; no nível do kernel.&lt;/p&gt;

&lt;h3&gt;
  
  
  A mágica do &lt;code&gt;SCM_RIGHTS&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;O &lt;code&gt;SCM_RIGHTS&lt;/code&gt; permite que o &lt;strong&gt;processo A&lt;/strong&gt; diga para o &lt;strong&gt;processo B&lt;/strong&gt;: &lt;em&gt;"Ei, pega essa conexão aqui e assume ela"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;No nível do kernel, isso significa:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Processo Nginx tem &lt;code&gt;FD=5&lt;/code&gt; apontando para uma struct de socket&lt;/li&gt;
&lt;li&gt;Via &lt;code&gt;SCM_RIGHTS&lt;/code&gt;, o kernel &lt;strong&gt;duplica essa referência&lt;/strong&gt; no processo do backend&lt;/li&gt;
&lt;li&gt;Agora o processo do backend também tem um FD (digamos &lt;code&gt;FD=3&lt;/code&gt;) apontando para &lt;strong&gt;a mesma struct de socket&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;É a &lt;strong&gt;mesma conexão TCP&lt;/strong&gt;, só que agora dois processos podem acessá-la&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Após a transferência:
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff6phf672lk6fj8j6rh6r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff6phf672lk6fj8j6rh6r.png" alt=" " width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;O Nginx "esquece" da conexão (fecha seu FD=5), e o backend assume toda a comunicação diretamente. &lt;strong&gt;Zero overhead de proxy&lt;/strong&gt;, &lt;strong&gt;zero cópias extras&lt;/strong&gt;, &lt;strong&gt;zero context switches&lt;/strong&gt; desnecessários.&lt;/p&gt;

&lt;p&gt;A operação é &lt;strong&gt;atômica&lt;/strong&gt; porque o kernel garante que a referência não seja perdida durante a transferência, e é &lt;strong&gt;zero-copy&lt;/strong&gt; porque não há movimento de dados, apenas manipulação de ponteiros internos do kernel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementação: A Stack Nginx + Lua + Go
&lt;/h2&gt;

&lt;p&gt;Nossa implementação utiliza uma combinação de tecnologias especificamente escolhidas para realizar um processo em etapas bem definidas:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Visão geral do que vamos fazer:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Nginx recebe a conexão&lt;/strong&gt; e realiza o handshake WebSocket completo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extraímos o file descriptor&lt;/strong&gt; da estrutura interna do Nginx usando FFI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transferimos esse FD&lt;/strong&gt; para o backend Go via Unix Domain Socket usando SCM_RIGHTS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go recebe o FD&lt;/strong&gt; e converte em uma conexão TCP nativa&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nginx substitui o FD original&lt;/strong&gt; por um dummy e sai de cena&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go assume completamente&lt;/strong&gt; a comunicação direta com o cliente&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Agora vamos implementar cada etapa:&lt;/p&gt;

&lt;h3&gt;
  
  
  Etapa 1: Nginx - Recepção e Handshake WebSocket
&lt;/h3&gt;

&lt;p&gt;O Nginx funciona como o ponto de entrada, realizando apenas o setup inicial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/ws/fd_transfer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;content_by_lua_block&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;local&lt;/span&gt; &lt;span class="s"&gt;websocket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;require&lt;/span&gt; &lt;span class="s"&gt;"resty.websocket.server"&lt;/span&gt;
        &lt;span class="s"&gt;local&lt;/span&gt; &lt;span class="s"&gt;ws,&lt;/span&gt; &lt;span class="s"&gt;err&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;websocket:new&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kn"&gt;timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;max_payload_len&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;
        &lt;span class="err"&gt;}&lt;/span&gt;

        &lt;span class="s"&gt;if&lt;/span&gt; &lt;span class="s"&gt;not&lt;/span&gt; &lt;span class="s"&gt;ws&lt;/span&gt; &lt;span class="s"&gt;then&lt;/span&gt;
            &lt;span class="s"&gt;ngx.log(ngx.ERR,&lt;/span&gt; &lt;span class="s"&gt;"failed&lt;/span&gt; &lt;span class="s"&gt;to&lt;/span&gt; &lt;span class="s"&gt;create&lt;/span&gt; &lt;span class="s"&gt;websocket:&lt;/span&gt; &lt;span class="s"&gt;",&lt;/span&gt; &lt;span class="s"&gt;err)&lt;/span&gt;
            &lt;span class="s"&gt;return&lt;/span&gt;
        &lt;span class="s"&gt;end&lt;/span&gt;

        &lt;span class="s"&gt;local&lt;/span&gt; &lt;span class="s"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;require&lt;/span&gt; &lt;span class="s"&gt;"ws_handler"&lt;/span&gt;
        &lt;span class="s"&gt;handler.handle(ws.sock)&lt;/span&gt;

        &lt;span class="s"&gt;ws:send_close()&lt;/span&gt;
    &lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quando o cliente conecta em &lt;code&gt;/ws/fd_transfer&lt;/code&gt; e solicita upgrade para WebSocket, o Nginx realiza o handshake WebSocket completo, estabelecendo uma conexão ativa com o cliente. &lt;/p&gt;

&lt;p&gt;E aqui entra o ponto crítico da solução: em vez de começar a fazer proxy das mensagens entre as duas pontas, o Nginx chama &lt;code&gt;handler.handle(ws.sock)&lt;/code&gt; que extrai o FD da conexão TCP subjacente e transfere via &lt;code&gt;SCM_RIGHTS&lt;/code&gt; para o backend através de Unix socket. Então substitui o FD original por um dummy e encerra sua participação com &lt;code&gt;ws:send_close()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Etapa 2: Extração do File Descriptor com FFI
&lt;/h3&gt;

&lt;p&gt;Outro ponto importante da implementação é a extração do FD da estrutura interna do Nginx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;get_socket_fd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;sock&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"invalid websocket object"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ffi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ngx_http_lua_socket_tcp_upstream_s*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"invalid upstream socket structure"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;tonumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esta função utiliza FFI (Foreign Function Interface) para acessar diretamente as estruturas internas do Nginx, extraindo o file descriptor da conexão TCP subjacente. É uma operação de baixo nível que "vasculha" a memória do processo Nginx para encontrar o número do file descriptor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Etapa 3: Transferência via SCM_RIGHTS
&lt;/h3&gt;

&lt;p&gt;O &lt;code&gt;fd_manager.lua&lt;/code&gt; implementa o protocolo de transferência:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;M&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_fd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;sock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AF_UNIX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOCK_STREAM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;-- Preparação da mensagem de controle SCM_RIGHTS&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;control_len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ffi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"struct cmsghdr"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ffi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"int"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;control&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ffi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"char[?]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;control_len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;cmsg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ffi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"struct cmsghdr*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;cmsg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cmsg_len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;control_len&lt;/span&gt;
    &lt;span class="n"&gt;cmsg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cmsg_level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOL_SOCKET&lt;/span&gt;
    &lt;span class="n"&gt;cmsg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cmsg_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SCM_RIGHTS&lt;/span&gt;

    &lt;span class="c1"&gt;-- Insere o FD na mensagem de controle&lt;/span&gt;
    &lt;span class="n"&gt;ffi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"int*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;control&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ffi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"struct cmsghdr"&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt;

    &lt;span class="c1"&gt;-- Envia via sendmsg&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ffi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"struct msghdr"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;msg_control&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;control&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;msg_controllen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;control_len&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sendmsg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esta função monta uma mensagem especial do tipo &lt;code&gt;SCM_RIGHTS&lt;/code&gt; que contém o file descriptor e a envia através de um Unix Domain Socket para o backend Go. É aqui que acontece a "transferência de propriedade" da conexão.&lt;/p&gt;

&lt;h3&gt;
  
  
  Etapa 4: Backend Go - Recepção e Conversão
&lt;/h3&gt;

&lt;p&gt;O backend Go implementa o receptor dos file descriptors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;receiveFD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UnixConn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;oob&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CmsgSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadMsgUnix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;oob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Parse das mensagens de controle&lt;/span&gt;
    &lt;span class="n"&gt;msgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParseSocketControlMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Extração do file descriptor&lt;/span&gt;
    &lt;span class="n"&gt;fds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParseUnixRights&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;msgs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uma vez recebido o FD, ele é convertido em uma &lt;code&gt;net.Conn&lt;/code&gt; nativa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uintptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"websocket_fd"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tcpConn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FileConn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c"&gt;// Agora tcpConn é uma conexão TCP normal&lt;/span&gt;
&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;handleConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tcpConn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;handleConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Leitura direta de frames WebSocket&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;wsutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadClientMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// Processamento frame por frame&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpCode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpText&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
                &lt;span class="c"&gt;// Acesso direto ao payload sem cópias&lt;/span&gt;
                &lt;span class="n"&gt;wsutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteServerText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpBinary&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;wsutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteServerBinary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A partir desse momento, o backend Go está falando diretamente com o cliente original, como se tivesse criado a conexão desde o início.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detalhes Técnicos Críticos
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Substituição por Socket Dummy
&lt;/h3&gt;

&lt;p&gt;Após transferir o FD, precisamos "enganar" o Nginx substituindo o file descriptor original &lt;br&gt;
 por um "dummy" para quando o nginx for liberar recursos não fechar o socket que agora pertence ao processo do backend.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;M&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace_with_dummy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;dummy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AF_INET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOCK_STREAM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dummy&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"failed to create dummy socket"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;-- dup2 substitui o FD original pelo dummy&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dup2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dummy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dummy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"dup2 failed"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dummy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Parsing de Frames WebSocket com &lt;code&gt;gobwas/ws&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Quando o backend Go recebe o file descriptor via &lt;code&gt;SCM_RIGHTS&lt;/code&gt;, ele herda uma conexão TCP que já está "promovida" para WebSocket pelo Nginx. Isso significa que:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O handshake HTTP → WebSocket já foi realizado&lt;/li&gt;
&lt;li&gt;A conexão está no estado WebSocket&lt;/li&gt;
&lt;li&gt;Todos os dados subsequentes são frames WebSocket&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Por isso, não podemos usar bibliotecas WebSocket convencionais como &lt;code&gt;gorilla/websocket&lt;/code&gt;, que esperam fazer o handshake completo desde o início. Precisamos de uma biblioteca que faça apenas o parsing dos frames WebSocket.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmarking: Dados Concretos de Performance
&lt;/h2&gt;

&lt;p&gt;O benchmark automatizado compara duas arquiteturas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Proxy Tradicional&lt;/strong&gt; (&lt;code&gt;/ws/proxy&lt;/code&gt;): Nginx faz proxy das mensagens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Com &lt;code&gt;SCM_RIGHTS&lt;/code&gt;&lt;/strong&gt; (&lt;code&gt;/ws/fd_transfer&lt;/code&gt;): Nginx transfere a conexão&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Metodologia
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ferramenta&lt;/strong&gt;: k6 para geração de carga&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conexões&lt;/strong&gt;: 10.000 WebSockets simultâneas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duração&lt;/strong&gt;: 60 segundos por teste&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoramento&lt;/strong&gt;: Prometheus com métricas de containers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Espera&lt;/strong&gt;: 15 segundos para estabilização da carga&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Resultados
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Backend CPU (cores)&lt;/th&gt;
&lt;th&gt;Backend Mem (MB)&lt;/th&gt;
&lt;th&gt;Nginx CPU (cores)&lt;/th&gt;
&lt;th&gt;Nginx Mem (MB)&lt;/th&gt;
&lt;th&gt;Active WS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/ws/proxy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.0170&lt;/td&gt;
&lt;td&gt;143.54&lt;/td&gt;
&lt;td&gt;0.0074&lt;/td&gt;
&lt;td&gt;331.89&lt;/td&gt;
&lt;td&gt;10000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/ws/fd_transfer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.0211&lt;/td&gt;
&lt;td&gt;116.43&lt;/td&gt;
&lt;td&gt;0.0005&lt;/td&gt;
&lt;td&gt;55.03&lt;/td&gt;
&lt;td&gt;10000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Análise dos Resultados
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Memória Nginx&lt;/strong&gt;: Redução de 331.89 MB → 55.03 MB (&lt;strong&gt;83.4% de redução&lt;/strong&gt;)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O handoff libera o Nginx de manter buffers para 10.000 conexões&lt;/li&gt;
&lt;li&gt;Memória residual reflete apenas overhead do processo base&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;CPU Nginx&lt;/strong&gt;: Redução de 0.0074 → 0.0005 cores (&lt;strong&gt;93.2% de redução&lt;/strong&gt;)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nginx não processa mensagens WebSocket após o handoff&lt;/li&gt;
&lt;li&gt;CPU residual é apenas para aceitar novas conexões&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Backend&lt;/strong&gt;: Ligeiro aumento de CPU (0.0170 → 0.0211), redução de memória (143.54 → 116.43 MB)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Aumento de CPU esperado: backend assume todo o I/O da rede&lt;/li&gt;
&lt;li&gt;Redução de memória: &lt;code&gt;gobwas/ws&lt;/code&gt; trabalha apenas com frames WebSocket e consome menos memória&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implicações Arquiteturais
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Vantagens
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Escalabilidade Horizontal&lt;/strong&gt;: Nginx pode aceitar muito mais conexões&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latência Reduzida&lt;/strong&gt;: Eliminação do hop intermediário&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Eficiência de Recursos&lt;/strong&gt;: Melhor utilização de CPU e memória&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separação de Responsabilidades&lt;/strong&gt;: Nginx foca em roteamento, backend em lógica&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Limitações
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Complexidade&lt;/strong&gt;: Implementação significativamente mais complexa&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependência de Plataforma&lt;/strong&gt;: Específico para sistemas Unix/Linux&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debugging&lt;/strong&gt;: Mais difícil rastrear conexões após handoff&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Estado Compartilhado&lt;/strong&gt;: Coordenação entre Nginx e backend é crítica&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Casos de Uso Ideais
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Aplicações de High-Frequency Trading&lt;/strong&gt;: Onde cada microssegundo importa&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jogos Multiplayer&lt;/strong&gt;: Latência crítica para experiência do usuário
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sistemas IoT&lt;/strong&gt;: Grande volume de conexões com mensagens pequenas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaming de Dados&lt;/strong&gt;: Onde throughput máximo é essencial&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;A passagem de FD via &lt;code&gt;SCM_RIGHTS&lt;/code&gt; entre processos pode ajudar aplicações que exigem performance extrema. Embora adicione complexidade, os ganhos em eficiência e escalabilidade são substanciais.&lt;/p&gt;

&lt;p&gt;Esta técnica não é adequada para todas as aplicações, mas para cenários onde performance é crítica, ela oferece uma vantagem competitiva significativa. À medida que as aplicações em tempo real se tornam mais críticas, dominar técnicas de baixo nível como &lt;code&gt;SCM_RIGHTS&lt;/code&gt; do kernel Linux se torna essencial para arquitetos de sistemas que buscam extrair o máximo desempenho da infraestrutura disponível.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>devops</category>
      <category>docker</category>
      <category>go</category>
    </item>
    <item>
      <title>How to Escape from a Container</title>
      <dc:creator>Luis Gustavo S. Barreto</dc:creator>
      <pubDate>Wed, 06 Aug 2025 19:01:56 +0000</pubDate>
      <link>https://dev.to/gustavosbarreto/how-to-escape-from-a-container-4p3i</link>
      <guid>https://dev.to/gustavosbarreto/how-to-escape-from-a-container-4p3i</guid>
      <description>&lt;p&gt;When I started designing &lt;a href="https://shellhub.io" rel="noopener noreferrer"&gt;ShellHub&lt;/a&gt;, one of the main goals was to make installation easy. I wanted users to be able to test and use the product with minimal barriers and friction. Even though the "users" were developers, I believed that the simpler the beginning, the greater the chance someone would give the project a try. And looking back, I think this was indeed one of the reasons for ShellHub's success.&lt;/p&gt;

&lt;p&gt;Right from the start, it became very clear that distributing ShellHub in containers would make things much easier. Because, let's face it, what developer today doesn't have Docker installed either on their local machine or on servers? This choice made everything simpler, from deployment to initial testing. And so it was.&lt;/p&gt;

&lt;p&gt;But this decision led me to face a major Linux engineering challenge. How could ShellHub, running inside a container, offer a shell that operated directly on the host operating system and not in the container's isolated environment?&lt;/p&gt;

&lt;p&gt;The solution I developed was a way to make a process escape from the container and be, in a way, "teleported" to the host. And when I say "escape," I'm not talking about exploiting security flaws or anything like that. What I built was a secure bridge, using Docker's own resources and Linux kernel tools. Everything within the rules.&lt;/p&gt;

&lt;p&gt;In this post, I want to tell you how I built this bridge. I divide it into two parts: the foundation (container configuration with Docker) and the crossing (what makes the magic happen in Linux).&lt;/p&gt;

&lt;h2&gt;
  
  
  The Complete Flow
&lt;/h2&gt;

&lt;p&gt;Before diving into the details, I'll show you how the general flow works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│     User        │───▶│   Container     │───▶│     Host        │
│   connects      │    │  (ShellHub      │    │  (real shell)   │
│                 │    │    Agent)       │    │                 │
└─────────────────┘    └─────────────────┘    └─────────────────┘
                              │                         ▲
                              │                         │
                              └──── nsenter + setpriv ──┘

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Part 1: The Foundation – Preparing the Container with Docker
&lt;/h2&gt;

&lt;p&gt;The foundation of the solution lies in how the agent container is started. Everything begins with &lt;code&gt;docker run&lt;/code&gt; and some flags that give the container sufficient permissions and visibility to see and interact with the real host.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Essential Flags
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;--privileged&lt;/code&gt;&lt;/strong&gt;: This flag gives the container elevated permissions within the kernel, allowing it to access devices, mount namespaces, use &lt;code&gt;setuid&lt;/code&gt;, among other things that normal containers can't do. It's the key that opens the doors for the operations we'll do later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;--pid=host&lt;/code&gt;&lt;/strong&gt;: This makes the container share the process namespace with the host. With this flag, the agent can see all processes running on the real system, including the &lt;code&gt;init&lt;/code&gt; process (PID 1), which will be fundamental in the next step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;--network=host&lt;/code&gt;&lt;/strong&gt;: This puts the container on the same network stack as the host, which eliminates the need to map ports and facilitates any communication the agent needs to make. It also ensures the shell has access to the same network the user would expect on the host.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Critical Volumes
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;-v /:/host&lt;/code&gt;&lt;/strong&gt;: This volume mounts the host's filesystem inside the container, at the &lt;code&gt;/host&lt;/code&gt; path. This is how the agent gets access to the entire real filesystem, from &lt;code&gt;/etc&lt;/code&gt; to &lt;code&gt;/home&lt;/code&gt;, &lt;code&gt;/var&lt;/code&gt;, and everything else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;-v /etc/passwd:/etc/passwd:ro&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;-v /etc/group:/etc/group:ro&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;-v /etc/shadow:/etc/shadow:ro&lt;/code&gt;&lt;/strong&gt;: These files are mounted so the agent can read the host's user data and know exactly who it's dealing with. Without this, it would be impossible to switch users correctly.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Complete Command
&lt;/h3&gt;

&lt;p&gt;The agent container is started with something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; shellhub-agent &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--privileged&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--pid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;host &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;host &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; /:/host &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; /etc/passwd:/etc/passwd:ro &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; /etc/group:/etc/group:ro &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; /etc/shadow:/etc/shadow:ro &lt;span class="se"&gt;\&lt;/span&gt;
  shellhub/agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, the container has everything: access to the host's filesystem, visibility of real processes, shared network, and sufficient permissions to start commands in the host context. The bridge is built. Now it's time to cross.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 2: The Crossing – How to Exit the Container Safely
&lt;/h2&gt;

&lt;p&gt;With host access guaranteed, the next step is to start a process that actually runs on the real system, even though it's initiated from within the container.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Main Tool: &lt;code&gt;nsenter&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The first tool that comes into play is &lt;code&gt;nsenter&lt;/code&gt; (namespace enter). With it, you can enter another process's namespaces. And since the container is using &lt;code&gt;--pid=host&lt;/code&gt;, it can see process PID 1 on the host (usually &lt;code&gt;systemd&lt;/code&gt; or &lt;code&gt;init&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;From there, the agent uses &lt;code&gt;nsenter&lt;/code&gt; to launch a new process already within the correct namespaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;mount&lt;/strong&gt;: Host filesystem&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;uts&lt;/strong&gt;: Host hostname and domainname&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ipc&lt;/strong&gt;: Host inter-process communication&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;net&lt;/strong&gt;: Host network stack&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pid&lt;/strong&gt;: Host process tree
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nsenter &lt;span class="nt"&gt;--target&lt;/span&gt; 1 &lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nt"&gt;--uts&lt;/span&gt; &lt;span class="nt"&gt;--ipc&lt;/span&gt; &lt;span class="nt"&gt;--net&lt;/span&gt; &lt;span class="nt"&gt;--pid&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Security Control: &lt;code&gt;setpriv&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The process born from &lt;code&gt;nsenter&lt;/code&gt; is already outside the container, running in the host context. But there's still a problem: this process would be executed as root, since it's started by the privileged container. That would give users way more privileges than they should have.&lt;/p&gt;

&lt;p&gt;That's where &lt;code&gt;setpriv&lt;/code&gt; comes in. With it, you can drop privileges and switch UID and GID before executing the shell. This way, the process assumes the real user identity on the system and doesn't carry any permissions it shouldn't have.&lt;/p&gt;

&lt;p&gt;Important &lt;code&gt;setpriv&lt;/code&gt; flags:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--reuid&lt;/code&gt;: Sets the real and effective UID&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--regid&lt;/code&gt;: Sets the real and effective GID&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--clear-groups&lt;/code&gt;: Removes all supplementary groups&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Final Command
&lt;/h3&gt;

&lt;p&gt;The final command that the agent dynamically builds looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nsenter &lt;span class="nt"&gt;--target&lt;/span&gt; 1 &lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nt"&gt;--uts&lt;/span&gt; &lt;span class="nt"&gt;--ipc&lt;/span&gt; &lt;span class="nt"&gt;--net&lt;/span&gt; &lt;span class="nt"&gt;--pid&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  setpriv &lt;span class="nt"&gt;--reuid&lt;/span&gt; 1001 &lt;span class="nt"&gt;--regid&lt;/span&gt; 1001 &lt;span class="nt"&gt;--clear-groups&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    /bin/bash &lt;span class="nt"&gt;--login&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enters the host via &lt;code&gt;nsenter&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Switches to the correct user via &lt;code&gt;setpriv&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Starts a clean shell with &lt;code&gt;--login&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Security Aspects
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What This Approach Mitigates
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Unnecessary isolation&lt;/strong&gt;: Instead of creating abstraction layers that would make usage difficult, we provide direct access to what the user needs, but in a controlled way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Privilege escalation&lt;/strong&gt;: Using &lt;code&gt;setpriv&lt;/code&gt; ensures that even though the agent runs as root in the container, the end user's shell has only the permissions it should have on the host.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context leakage&lt;/strong&gt;: Since we use all host namespaces, there's no confusion between the container environment and the real host environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why &lt;code&gt;setpriv&lt;/code&gt; is Crucial
&lt;/h3&gt;

&lt;p&gt;Without &lt;code&gt;setpriv&lt;/code&gt;, any process started by the agent inherits root permissions. This is a serious security problem, as it would give the end user more privileges than they should have.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;setpriv&lt;/code&gt; works as a "safety valve" that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Removes unnecessary capabilities&lt;/li&gt;
&lt;li&gt;Sets correct UID/GID&lt;/li&gt;
&lt;li&gt;Clears supplementary groups&lt;/li&gt;
&lt;li&gt;Ensures the child process cannot recover privileges&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Limitations and Precautions
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Privileged container&lt;/strong&gt;: Using &lt;code&gt;--privileged&lt;/code&gt; is necessary but brings responsibilities. The agent needs to be trustworthy, since it has full access to the host.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attack surface&lt;/strong&gt;: Although the final shell is secure, the agent itself has elevated privileges. It's crucial to keep the agent code simple and auditable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kernel dependencies&lt;/strong&gt;: The solution depends on specific Linux kernel features (&lt;code&gt;nsenter&lt;/code&gt;, namespaces, etc). It doesn't work on FreeBSD which has a different installation method.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Complete Flow in Practice
&lt;/h2&gt;

&lt;p&gt;In practice, when a user connects to ShellHub, what happens is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Connection reception&lt;/strong&gt;: The agent, running inside the container, receives the SSH connection request.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;User identification&lt;/strong&gt;: The agent queries the &lt;code&gt;/etc/passwd&lt;/code&gt; and &lt;code&gt;/etc/group&lt;/code&gt; files (which are mounted from the host) to identify the correct UID/GID for the user.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Command assembly&lt;/strong&gt;: It dynamically builds an &lt;code&gt;nsenter&lt;/code&gt; + &lt;code&gt;setpriv&lt;/code&gt; command specific to that user:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   nsenter &lt;span class="nt"&gt;--target&lt;/span&gt; 1 &lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nt"&gt;--uts&lt;/span&gt; &lt;span class="nt"&gt;--ipc&lt;/span&gt; &lt;span class="nt"&gt;--net&lt;/span&gt; &lt;span class="nt"&gt;--pid&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     setpriv &lt;span class="nt"&gt;--reuid&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;user_uid&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;--regid&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;user_gid&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;--clear-groups&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
       &lt;span class="o"&gt;{&lt;/span&gt;user_shell&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;--login&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Execution and bridging&lt;/strong&gt;: The command is executed, creating a process that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runs in the host context (thanks to &lt;code&gt;nsenter&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Has the correct user permissions (thanks to &lt;code&gt;setpriv&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Offers a fully functional shell on the real system&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Communication&lt;/strong&gt;: The stdin/stdout/stderr of this process are connected to the SSH session, creating a transparent experience for the user.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This entire process happens securely, without exploits and without tricks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Engineering Lessons
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Simplicity as a non-functional requirement&lt;/strong&gt;: Treating ease of use as a real technical requirement, not as "nice to have," forced more creative solutions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limitations generate innovation&lt;/strong&gt;: The decision to use Docker created a technical limitation that resulted in a more elegant solution than traditional alternatives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tools exist for a reason&lt;/strong&gt;: Instead of reinventing the wheel, creatively combining &lt;code&gt;nsenter&lt;/code&gt; and &lt;code&gt;setpriv&lt;/code&gt; solved a complex problem with minimal code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security from the start&lt;/strong&gt;: Thinking about security from conception (with &lt;code&gt;setpriv&lt;/code&gt;) avoided rework and future problems.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The decision to use Docker, which initially was just to make installation easier, ended up forcing me to solve a deep technical problem. And the solution I found is one of the parts of ShellHub I'm most proud of. No tricks. Just engineering, reasoning, and creative use of tools that already existed.&lt;/p&gt;

&lt;p&gt;If you're facing a similar problem, remember: sometimes the best solution is combining existing tools in new ways, instead of building something from scratch. Linux already gives you the pieces, you just need to know them well enough to put them together creatively.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>linux</category>
    </item>
    <item>
      <title>drera-labs: Terminal website</title>
      <dc:creator>Luis Gustavo S. Barreto</dc:creator>
      <pubDate>Tue, 29 Jul 2025 19:16:55 +0000</pubDate>
      <link>https://dev.to/gustavosbarreto/drera-labs-terminal-website-4ecn</link>
      <guid>https://dev.to/gustavosbarreto/drera-labs-terminal-website-4ecn</guid>
      <description>&lt;p&gt;I built a personal website that isn't a typical website. Instead of using HTML and CSS, it runs entirely inside a terminal user interface (TUI). You can access it in two ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;From your browser:&lt;/strong&gt; &lt;a href="https://www.dreralabs.xyz" rel="noopener noreferrer"&gt;www.dreralabs.xyz&lt;/a&gt; (which runs in a terminal emulator over WebSocket)&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Directly from your terminal:&lt;/strong&gt; &lt;code&gt;ssh www.dreralabs.xyz&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this post, I'll show you how this crazy idea works, the architecture behind it, and the tools that made it possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Idea: Why a Website in the Terminal?
&lt;/h2&gt;

&lt;p&gt;As a Linux and command-line enthusiast, I spend most of my day in a terminal. The idea for Drera Labs came from a question: "What if my personal website was an extension of my favorite work environment?".&lt;/p&gt;

&lt;p&gt;I wanted to create something that was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Fast and lightweight:&lt;/strong&gt; Without the overhead of heavy JavaScript frameworks.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Interactive:&lt;/strong&gt; Using TUI tools to create a "glamorous" terminal experience.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Accessible:&lt;/strong&gt; Available to those who prefer the convenience of the web and those who live in the terminal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is a site where navigation, reading posts, and even submitting forms happen in a shell environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works: The Architecture
&lt;/h2&gt;

&lt;p&gt;The project's magic lies in unifying web and SSH access into a single terminal experience. The backend is written in &lt;strong&gt;Go&lt;/strong&gt; and orchestrates everything.&lt;/p&gt;

&lt;p&gt;Here's a summary of the architecture:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dual Entry Point:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  An &lt;strong&gt;HTTP/WebSocket server&lt;/strong&gt; (port 8080) serves a minimal HTML page that opens a WebSocket connection.&lt;/li&gt;
&lt;li&gt;  An &lt;strong&gt;SSH server&lt;/strong&gt; (port 2221) accepts direct connections from SSH clients.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;WebSocket to SSH Proxy:&lt;/strong&gt;&lt;br&gt;
On the frontend, the browser runs a terminal emulator built with &lt;strong&gt;xterm.js&lt;/strong&gt;. When a client connects via the browser, the Go server doesn't interpret the commands. Instead, it acts as a &lt;em&gt;proxy&lt;/em&gt;, opening a connection to its own local SSH server. This is the key trick: &lt;strong&gt;every interaction, whether from the web or SSH, is treated as a standard SSH session&lt;/strong&gt;. This vastly simplifies the logic, as we only need to worry about managing SSH sessions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Session Management with Zellij:&lt;/strong&gt;&lt;br&gt;
For each new connection, the server starts a &lt;strong&gt;Zellij&lt;/strong&gt; session, a modern terminal multiplexer. Zellij is configured with two main files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;layout.kdl&lt;/code&gt;: Defines the interface structure, splitting the screen into panes for the navigation bar, content area, and status bars.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;config.kdl&lt;/code&gt;: Customizes the theme, shortcuts, and overall behavior of Zellij.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Navigation with Shell Scripts and &lt;code&gt;gum&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
Navigation is controlled by a series of shell scripts that communicate through a &lt;em&gt;named pipe&lt;/em&gt; (FIFO).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  The navigation pane runs a script that uses &lt;a href="https://github.com/charmbracelet/gum" rel="noopener noreferrer"&gt;&lt;code&gt;gum&lt;/code&gt;&lt;/a&gt;, a tool for creating "glamorous" TUIs, to display a menu of options.&lt;/li&gt;
&lt;li&gt;  When an option is selected, the name of the corresponding script (e.g., &lt;code&gt;blog.sh&lt;/code&gt;) is written to the &lt;em&gt;pipe&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;  The main content pane has a "router" script that continuously reads from the &lt;em&gt;pipe&lt;/em&gt;. When it receives a new command, it executes the appropriate script to display the content for that section.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Content Rendering with &lt;code&gt;glow&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
The pages and blog posts are written in Markdown. To display them beautifully in the terminal, the scripts use &lt;a href="https://github.com/charmbracelet/glow" rel="noopener noreferrer"&gt;&lt;code&gt;glow&lt;/code&gt;&lt;/a&gt;, which renders Markdown on the command line with style.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Backend:&lt;/strong&gt; Go

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Web/WebSocket Server:&lt;/strong&gt; &lt;code&gt;gorilla/websocket&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;SSH Server:&lt;/strong&gt; &lt;code&gt;gliderlabs/ssh&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;  &lt;strong&gt;Frontend:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Terminal Emulator:&lt;/strong&gt; xterm.js &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;  &lt;strong&gt;Terminal UI:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Multiplexer:&lt;/strong&gt; Zellij&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;TUI Tools:&lt;/strong&gt; &lt;code&gt;gum&lt;/code&gt; (for menus, forms, etc.)&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Markdown Renderer:&lt;/strong&gt; &lt;code&gt;glow&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;  &lt;strong&gt;Scripting Language:&lt;/strong&gt; Shell (Bash)&lt;/li&gt;

&lt;li&gt;  &lt;strong&gt;Containerization:&lt;/strong&gt; Docker and HAProxy&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Drera Labs is a fun experiment that explores the boundaries between the web and the terminal. It shows that it's possible to create rich, interactive experiences using only the tools that many of us already love and use every day.&lt;/p&gt;

&lt;p&gt;The project is open source, and you can check out the full repository on GitHub: &lt;a href="https://github.com/gustavosbarreto/drera-labs" rel="noopener noreferrer"&gt;github.com/gustavosbarreto/drera-labs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to explore, give feedback, or even fork it to create your own version. And the next time you think about building a website, maybe consider building it for the terminal!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>linux</category>
      <category>cli</category>
    </item>
    <item>
      <title>Why Use ShellHub Instead of a Traditional SSH Server?</title>
      <dc:creator>Luis Gustavo S. Barreto</dc:creator>
      <pubDate>Mon, 26 Dec 2022 17:45:37 +0000</pubDate>
      <link>https://dev.to/gustavosbarreto/why-use-shellhub-instead-of-a-traditional-ssh-server-39bk</link>
      <guid>https://dev.to/gustavosbarreto/why-use-shellhub-instead-of-a-traditional-ssh-server-39bk</guid>
      <description>&lt;p&gt;ShellHub is a centralized SSH gateway that allows users to remotely access and manage their servers and devices from anywhere, using a web browser or a mobile app. It provides a secure and convenient way to connect to and control your servers and devices, and can help to improve the security of your servers by preventing unauthorized access.&lt;/p&gt;

&lt;h3&gt;
  
  
  So why use ShellHub instead of a traditional SSH server? Here are some reasons:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Easy access:&lt;/strong&gt; With ShellHub, you can access your devices and servers from anywhere, using a web browser or a mobile app. This is more convenient than connecting to a traditional SSH server, which requires you to know the server's public IP address and possibly configure the router to allow SSH connections.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enhanced security:&lt;/strong&gt; ShellHub offers various security measures to protect your devices and servers. For example, it allows you to create firewall rules to filter SSH connections, only allowing the connections you authorize. It also supports public-key authentication, which can help prevent unauthorized access. In comparison, a traditional SSH server may be more susceptible to hacker attacks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simplified access management:&lt;/strong&gt; ShellHub allows you to create user accounts for each person who needs to access your devices and servers. This can make it easier to manage access, as you can grant or revoke access for individual users without affecting the access of other users. In comparison, a traditional SSH server may be more difficult to manage, as all people who need to access the server need to share the same password or use a private key.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Monitoring and tracking:&lt;/strong&gt; ShellHub offers audit logging and session recording features, which allow you to monitor and track access to your devices and servers. This can be useful for identifying unauthorized access attempts or for monitoring user activity on your devices. In comparison, a traditional SSH server may be more difficult to monitor and track.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In summary, ShellHub is a valuable tool that offers many advantages over a traditional SSH server for remotely accessing and managing your servers and devices. It allows you to easily access Linux devices behind a firewall and NAT, use standard tools to connect to devices and transfer files securely, manage access to devices using public-key authentication, protect your devices with flexible firewall rules, and monitor access and user activity with audit logging and session recording features.&lt;/p&gt;

&lt;p&gt;If you're interested in trying out ShellHub, you have two options: you can either test out the ShellHub Cloud for free, or follow the documentation to set up the Community Edition on your own server.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;ShellHub Cloud&lt;/strong&gt; is a SaaS (Software as a Service) offering that provides a convenient way to manage your servers and devices from anywhere. It's easy to get started: simply &lt;a href="https://cloud.shellhub.io/" rel="noopener noreferrer"&gt;sign up for a free account&lt;/a&gt; and you can start using ShellHub right away.&lt;/p&gt;

&lt;p&gt;If you prefer to self-host ShellHub, you can &lt;a href="https://docs.shellhub.io/self-hosted/deploying" rel="noopener noreferrer"&gt;follow the documentation&lt;/a&gt; to set up the &lt;strong&gt;Community Edition&lt;/strong&gt; on your own server. The Community Edition is open-source and includes the core features of ShellHub.&lt;/p&gt;

&lt;p&gt;In addition to the Community and Cloud, ShellHub also offers an &lt;strong&gt;Enterprise Edition&lt;/strong&gt; that is designed for use in a business environment. The Enterprise edition includes all of the features of the Community edition, as well as additional features that are specifically geared towards business use. These features may include things like firewall rules and session recording.&lt;/p&gt;

&lt;p&gt;One of the main benefits of the Enterprise Edition is that it can be installed on a dedicated infrastructure, which can provide greater reliability and performance. By running ShellHub on its own dedicated servers or virtual machines, you can ensure that it has the resources it needs to operate smoothly and effectively. This can be especially useful if you have a large number of devices and users that need to access your servers and devices through ShellHub.&lt;/p&gt;

&lt;p&gt;Overall, the Enterprise Edition of ShellHub is a powerful and flexible solution that offers a range of features and benefits for businesses. If you need a robust and reliable SSH gateway that can support your business needs, the Enterprise Edition may be the right choice for you.&lt;/p&gt;

&lt;p&gt;No matter which option you choose, ShellHub can help you easily and securely access and manage your servers and devices from anywhere. So why wait? Try ShellHub today and see for yourself how it can make your remote access experience better!&lt;/p&gt;

&lt;p&gt;If you're interested in contributing to the development of ShellHub, or just want to stay up to date with the latest news and updates, we invite you to join us on &lt;a href="https://github.com/shellhub-io/shellhub/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>devops</category>
      <category>cloud</category>
      <category>security</category>
    </item>
    <item>
      <title>Tutorial: How to access Raspberry Pi remotely from anywhere</title>
      <dc:creator>Luis Gustavo S. Barreto</dc:creator>
      <pubDate>Wed, 17 Aug 2022 22:53:00 +0000</pubDate>
      <link>https://dev.to/gustavosbarreto/tutorial-how-to-access-raspberry-pi-remotely-from-anywhere-1abl</link>
      <guid>https://dev.to/gustavosbarreto/tutorial-how-to-access-raspberry-pi-remotely-from-anywhere-1abl</guid>
      <description>&lt;p&gt;In this article, we’ll share a brief tutorial to help you access your Raspberry Pi remotely, easily, and fast using a centralized SSH server. We can assure you that our step-by-step guide will make the process much easier.&lt;/p&gt;

&lt;p&gt;Once ShellHub can improve your development process, Raspberry Pi boards are a great option to help you achieve success in your projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;On the Raspberry terminal type the following command to install Docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt install docker.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To launch the Docker, run the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl enable --now docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, set up the privileges for the current user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo usermod -aG docker $USER
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now reboot the Raspberry Pi to apply the changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we’re ready to install ShellHub!&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating an account on ShellHub
&lt;/h3&gt;

&lt;p&gt;Installing ShellHub on your device is very easy. But before starting to add your devices and enjoy all features available, it’s necessary to create an account on the platform. It’s for free, no credit card is required.&lt;/p&gt;

&lt;p&gt;To do that, you only need to access &lt;a href="https://cloud.shellhub.io/" rel="noopener noreferrer"&gt;ShellHub Cloud&lt;/a&gt; and register yourself.&lt;/p&gt;

&lt;p&gt;After registering, you’re able to add up to 3 devices to your account and access them whenever you need them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing ShellHub Agent
&lt;/h2&gt;

&lt;p&gt;Now, it’s time to add the ShellHub agent to the RPI. When you access your account, a wizard will appear, showing the following steps to add devices to your account. Just copy the command, paste into Raspberry PI terminal and run it.&lt;/p&gt;

&lt;p&gt;Your device will appear on the screen and you will have to confirm identity by clicking on accept button.&lt;/p&gt;

&lt;p&gt;And we finished. Now You can access your RPI in an easy, fast, and safe way. If you have any doubts, feel free to check our &lt;a href="https://docs.shellhub.io" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; or contact us at &lt;a href="mailto:contact@shellhub.io"&gt;contact@shellhub.io&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you’re curious to know more about Shellhub, follow us on &lt;a href="https://twitter.com/ShellHub_" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; and &lt;a href="https://www.linkedin.com/company/shellhub-ssh/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>devops</category>
      <category>raspberrypi</category>
      <category>security</category>
    </item>
  </channel>
</rss>
