I recently found myself in a situation, that some of you might have been in before. I had been asked to integrate with a client built API with my system. However! there was a catch, the API was only accessible from within their network, and a VPN was not possible.

So how best to interact with their systems?

As someone who uses Docker ALOT the first idea I had, was to create an image which contained ubuntu with a GUI and all of the features which I needed including running a VNC server. This meant I could forward my local port to the port on the server via an SSH command and connect to the GUI. Great! Or so I thought, after some time it was cumbersome, there was no copy and paste, and worst of all no debugger...

This got me thinking, so I did some research and came up with a plan...

As I was going to be doing this work in Ruby, was there something I could do using my IDE of choice and some clever workings using Docker? well the answer to that is yes. Bear in mind it could be a bit over the top but it works! :)

The Setup

I should also mention, that with this setup the client was not allowing me to install anything additional to their server. It was already running docker with a couple of my containers in place, so I knew I could run another one.

Using Docker I created an image from the official Alpine Linux with ruby 2.4.0 I also included some tools which I required, such as bash and openssh.

FROM ruby:2.4.0-alpine

RUN apk add --update \
  bash \
  openssh \
  build-base \
  libxml2-dev \
  libxslt-dev \
  ca-certificates \
  postgresql-dev \
  && mkdir -p ~root/.ssh && chmod 700 ~root/.ssh/ && \
  echo -e "Port 22\n" >> /etc/ssh/sshd_config && \
  cp -a /etc/ssh /etc/ssh.cache \
  && rm -rf /var/cache/apk/*

EXPOSE 22

COPY entry.sh /entry.sh

ENTRYPOINT ["/entry.sh"]

CMD ["/usr/sbin/sshd", "-D", "-f", "/etc/ssh/sshd_config"]

This also runs a custom shell script which starts up the sshd deamon for openssh.

Once built and pushed to hub.docker.com

I was ready to deploy this to the server with a simple mapping of ports and volumes:

docker run -d --name remote-debugger -p 22222:22 -p 26162:26162 -v ~/debug_keys:/root/.ssh/authorized_keys -v /opt/ol:/opt/ol ol/alpine-ruby-2.3.0-sshd-base:latest

To break this command down:

  • -p 22222:22 this will bind 22222 external to the container to port 22 internally
  • -p 26162:26162 this binds 26162 from both sides, and is used for debugging later
  • -v ~/debug_keys:/root/.ssh/authorized_keys this maps the left (host) to the right (container) this is used by the SSH client to allow connection, for this to work I copied my local id_rsa.pub contents to the debug_keys file on the server
  • -v /opt/ol:/opt/ol This mapped some persistence incase I wanted to restart the container

Once I had this done I was ready to setup my tunnel into the server, to do this I made an edit to my ~/.ssh/config

host RemoteServer01
    Hostname {ip}
    User {username}
    IdentityFile {the pem to get on server}
    LocalForward 127.0.0.1:26162 127.0.0.1:26162
    LocalForward 127.0.0.1:2222 127.0.0.1:22222

To explain the LocalForward lines: I am forwarding any thing on my machine localhost:26162 and 2222 to the remote server through the ssh tunnel.

once setup I could do the following:
ssh RemoteServer01

and in a separate terminal tab:
ssh -p 2222 [email protected]

Where the second command opened an SSH connection into the hosted remote docker container.

Once connected, I installed the debugging gem using: gem install ruby-debug-ide

Now I was almost ready to start developing my scripts and debugging them remotely!

Setting up RubyMine

Now that this had been setup, using RubyMine IDE I was able to create a remote debugging run configuration. To set this up chose: Run -> Edit configurations

Then select the "+" and Ruby remote debug

Give the configuration a name, then fill the boxes with the required information:

The configuration will also give you a command to run something similar to:

rdebug-ide --host 0.0.0.0 --port 26162 --dispatcher-port 26162 -- $COMMAND$

Where command is replaced with the path to the file to debug.

This is then run on the server (the Docker container), so say I had a file called test.rb I would run this command via an SSH connection: rdebug-ide --host 0.0.0.0 --port 26162 --dispatcher-port 26162 -- /opt/ol/test.rb

This should show something like:
Fast Debugger (ruby-debug-ide 0.6.0, debase 0.2.1, file filtering is supported) listens on 0.0.0.0:26162 on the server

Excellent we are almost there!

Then using something like the built in ftp/deployment tools in Rubymine, take a copy of the file, or edit it locally and push remotely.

Place some break points.

Then run your earlier created configuration, and boom! you are now debugging a remote script which is sat inside a docker container hosted on a clients locked down network.

Enjoy!