Reverse SSH Tunneling Made Easy with Docker With Topo

- 31 August 2018 - 7 mins read

If you’ve ever needed to show a client your local development environment or test a webhook integration, you know the pain of trying to expose a local service to the Internet. There are tools like ngrok and serveo that help, but what if you want full control of the tunneling server? Or what if you just want a dead-simple Docker-based setup that you can drop into any existing project?

That’s why I built Topo. The name means “mole” in Spanish (because it digs tunnels). Get it?

The Problem

During development, there are plenty of situations where you need your local machine to be accessible from the outside world:

  • Showing progress to a client without deploying to a staging server
  • Testing webhooks from third-party services (payment gateways, messaging platforms, etc.)
  • Sharing your local dev environment with a colleague for quick debugging
  • Testing how your application behaves behind a real domain with SSL

The usual approach involves either paying for a tunneling service, using a free tier with limitations, or setting up complex SSH configurations manually. I wanted something that was self-hostable, containerized, and easy to plug into existing Docker-based projects.

What It Does

Topo is a Dockerized reverse SSH tunneling solution with two components:

  • Server (roura/topo:server) - A self-hosted serveo instance that you run on your own domain. You control the tunnel endpoint.
  • Client (roura/topo:client) - A lightweight Alpine-based container that establishes the tunnel. It’s designed to be added to any existing docker-compose.yml file alongside your application.

The idea is simple: you host the server on a machine with a public IP and a domain, then use the client container locally to tunnel traffic from that domain straight to your local service.

How It Works

The client container runs a small shell script that establishes a reverse SSH tunnel:

ssh \
  -R $NAME.$SERVICE:$REMOTE_PORT:$CONTAINER:$HOST_PORT \
  -p $SERVICE_PORT \
  -o ServerAliveInterval=30 \
  -o ServerAliveCountMax=3 \
  -o StrictHostKeyChecking=no \
  -o TCPKeepAlive=yes \
  $SERVICE

It connects to the server, tells it to forward traffic from $NAME.$SERVICE (e.g., topo.yourdomain.com) back to a local container on your Docker network. The ServerAliveInterval and TCPKeepAlive options ensure the tunnel stays alive even through network hiccups.

Getting Started

Using the Client

The quickest way to use Topo is by adding the client to your existing docker-compose.yml. Here’s an example that tunnels a PHP/Apache container:

version: '3.6'

services:
  httptunnel:
    image: roura/topo:client
    network_mode: bridge
    container_name: httptunnel
    tty: true
    stdin_open: true
    command: sh connect
    links:
    - web
  web:
    image: php:apache
    network_mode: bridge
    container_name: website

The tunnel container links to your web service and forwards traffic to it. You can replace php:apache with whatever your application image is.

Hosting Your Own Server

If you want full control, you can self-host the server component on any machine with a public IP:

version: '3.6'

services:
  proxy:
    build:
      context: .
    network_mode: bridge
    container_name: tunnelserver
    command: sh start
    restart: on-failure
    ports:
    - 80:80
    - 443:443
    environment:
      SERVICE: your.domain-name.here

Point your domain’s DNS to the server, deploy the container, and you’ve got your own tunneling infrastructure. No third-party dependencies, no usage limits.

Configuration

The client is configured through environment variables:

Variable Description Default
SERVICE_PORT SSH port on the tunnel server 443
HOST_PORT Port of your local service 80
REMOTE_PORT Port to expose on the server 80
CONTAINER Name of the linked container web
NAME Subdomain prefix topo
SERVICE Tunnel server domain serveo.net

By default, Topo uses serveo.net as the tunneling service, so you can use it out of the box without hosting your own server. When you’re ready for full control, just change the SERVICE variable to your own domain.

Why Docker?

Wrapping the tunnel in a Docker container has a few practical advantages:

  1. No local dependencies - You don’t need SSH configured on your host machine
  2. Composable - Drop it into any docker-compose.yml alongside your existing services
  3. Isolated - The tunnel runs in its own container, separate from your application
  4. Portable - Works the same on any OS that runs Docker

Limitations

  • The tunnel relies on SSH, so it’s subject to network conditions and SSH connection limits
  • The self-hosted server requires a machine with a public IP and a domain
  • Not designed for high-traffic production use - this is a development tool
  • The server component bundles a serveo binary, which hasn’t been updated in a while

Try It Out

The project is available at github.com/rouralberto/topo. If you’re already using Docker for local development and need a quick way to expose services to the Internet, just add the Topo client to your compose file and you’re set. And if you want to go all-in, host your own server and own the entire pipeline.


Share: Link copied to clipboard

Tags:

Previous: Launch an Alibaba Cloud ECS with Docker using Terraform
Next: Going Serverless with Alibaba Cloud [Meetup]

Where: Home > Technical > Reverse SSH Tunneling Made Easy with Docker With Topo