I’ve been building a CI/deployment stack with GitLab CI, Docker, and Capistrano. I’m hoping to give a talk on this in the near future, but I wanted to add a brief note on a problem I solved using SSH agent forwarding in Docker in case anyone else runs into it.

In brief, I have code flowing like this:

  1. Push from local development environment to GitLab
  2. GitLab CI spins up a Docker container running Capistrano
  3. Capistrano deploys code to my staging environment via SSH

To do this elegantly requires a deploy user on the staging environment whose SSH key has read access to the repository on GitLab. I don’t want to deploy the private key to the remote staging server. The deploy tasks fire from the Docker container so we bake the private key into it:

# setup deploy key
RUN mkdir /root/.ssh
ADD repo-key /root/.ssh/id_rsa
RUN chmod 600 /root/.ssh/id_rsa
ADD config /root/.ssh/config

That part is relatively straightforward. Copy the private key into wherever you’re building the docker image (do not version it) and you’re good to go. Forwarding the key is trickier. First, you’ll have to tell Capistrano to use key forwarding in each deploy step. In 3.4.0 that looks like this:

 set :ssh_options, {
   forward_agent: true
 }

Next, you’ll have to bootstrap agent forwarding in your CI task. In a CI environment the docker container starts fresh every time and has even less state than usual. You need to start the agent and add your identity every time the task runs. See StackOverflow for a long discussion of agents and identifies. TLDR; add this step:

before_script:
 - eval `ssh-agent -s` && ssh-add

I experimented with adding that command to my Dockerfile but it didn’t work. This was the most common error: “Could not open a connection to your authentication agent.” The command has to be executed in the running container, which means in this case the CI task configuration, or the key won’t be forwarded and clones on the staging environment will fail with publickey denied.