Using SSH for deployment in Codeship Pro

Thomas Toye
Thomas Toye
Published in
3 min readDec 27, 2018

--

Using SSH is a simple way to run commands on a remote server. For example, to update a Docker Swarm stack after a successful build.

Photo by James & Carol Lee on Unsplash

Current solution

At the moment, loading build arguments from an encrypted file does not work correctly on Codeship.

Create a deployment service

This service will be responsible for connecting to the remote host. Create the following Dockerfile for the deployment service:

FROM ubuntu:bionic# Other commands, e.g. copy docker-compose.yml
# ...
RUN echo $'echo mkdir ~/.ssh \n\
echo ${DEPLOYMENT_SSH_PRIVATE_KEY_BASE64} | base64 -d > ~/.ssh/id_rsa \n\
chmod 600 ~/.ssh/id_rsa' > /tmp/get_ssh_id.sh
RUN chmod +x /tmp/get_ssh_id.sh
RUN apt-get update && apt-get install -y -qq ssh
CMD ["echo", "Image built!"]

This will create a script in /tmp that will get the SSH private key from an environment variable and install it. Now create the Codeship service. Add this to your codeship-services.yml:

# Image that will perform the deployment
# Needed env args: DEPLOYMENT_SSH_PRIVATE_KEY_BASE64
my-deployment-service:
build:
image: my-deployment-service
dockerfile: Dockerfile.deployment-service

encrypted_env_file: deployment-service.env_args.encrypted

Create an encrypted environment arguments file

Environment argument files do not support multiline string. An easy solution to this is to encode the SSH private key in base64 (this does not encrypt the key). Create deployment-service.env_args:

DEPLOYMENT_SSH_PRIVATE_KEY_BASE64=long_base64_encoded_string

Encrypt it:

jet encrypt --key-path codeship.aes deployment-service.env_args deployment-service.env_args.encrypted

Don’t forget to delete the unencrypted file.

Using the service in Codeship steps

Now you can use SSH and SCP in your steps, with one extra step: run the /tmp/get_ssh_id.sh script first. This means wrapping your commands in /bin/bash -c "/tmp/get_ssh_id.sh && your_command here". Example:

- name: copy_docker_compose_file
service: my-deployment-service
command: "/bin/bash -c \"/tmp/get_ssh_id.sh && /usr/bin/scp -oStrictHostKeyChecking=accept-new /tmp/docker-compose.prod.yml deploy@my.app:~/\""
tag: release

It’s not pretty, but it works.

How it should work: build arguments

This doesn’t work. The encrypted_build_args directive does not work correctly: it doesn’t load the build arguments. When Codeship fixes this, this (more elegant) way will work.

Create a deployment service

This service will be responsible for connecting to the remote host. Create the following Dockerfile for the deployment service:

FROM ubuntu:bionic

ARG SSH_PRIVATE_KEY_BASE64
# Other commands, e.g. copy docker-compose.yml
# ...
RUN mkdir ~/.ssh
RUN echo ${SSH_PRIVATE_KEY_BASE64} | base64 -d > ~/.ssh/id_rsa
RUN chmod 600 ~/.ssh/id_rsa
RUN apt-get update && apt-get install -y -qq ssh

CMD ["echo", "Image works"]

Now create the Codeship service. Add this to your codeship-services.yml:

# Image that will perform the deployment
# Needed build args:
SSH_PRIVATE_KEY_BASE64
my-deployment-service:
build:
image: my-api-deployment-service
dockerfile: Dockerfile.deployment-service
encrypted_build_args: deployment-service.build_args.encrypted

Create an encrypted build arguments file

Build argument files do not support multiline strings. An easy solution to this is to encode the SSH private key in base64 (this does not encrypt the key). Create deployment-service.build_args:

SSH_PRIVATE_KEY_BASE64=long_base64_encoded_string

Encrypt it:

jet encrypt --key-path codeship.aes deployment-service.build_args deployment-service.build_args.encrypted

Don’t forget to delete the unencrypted file.

Using the service in Codeship steps

Now that we have the service, we can use ssh and scp in codeship-steps.yml:

- name: copy_docker_compose_file
service: my-deployment-service
command: '/usr/bin/scp -oStrictHostKeyChecking=accept-new /tmp/docker-compose.prod.yml deploy@your.app:~/'
tag
: release
- name: pull_new_images
service: my-deployment-service
command: "/usr/bin/ssh -oStrictHostKeyChecking=accept-new deploy@your.app 'docker-compose -f docker-compose.prod.yml pull'"
tag
: release
- name: update_stack
service: my-deployment-service
command: "/usr/bin/ssh -oStrictHostKeyChecking=accept-new deploy@your.app 'docker stack deploy --compose-file docker-compose.prod.yml my-api --with-registry-auth'"
tag
: release

-oStrictHostKeyChecking=accept-new will auto-accept the server’s SSH key. You can add a known_hosts file with the server’s public key.

--

--