Skip to content

Instantly share code, notes, and snippets.

@MBuffenoir
Last active March 7, 2017 22:14
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MBuffenoir/9311ccbea4b18f1d170bed10a7b96d7b to your computer and use it in GitHub Desktop.
Save MBuffenoir/9311ccbea4b18f1d170bed10a7b96d7b to your computer and use it in GitHub Desktop.

Introduction

This is the last tutorial in our series about creating CICD pipelines with Docker containers.

Now that our little flask application is tested by Travis, let's see together how we can make sure a docker image is being build and pushed to the hub, instead of using the autobuild feature. Then we will see how to deploy the latest built image automatically each time some code is added to the project.

I will assume in this tutorial that you already have a remote host set up and that you have access to it through ssh. To create the examples, I have been using an host running on Ubuntu, but it should be very easy to adapt the following to any popular linux distribution.

Requirements

First things first you will need to have the travis command line client installed on your workstation (make sure the latest version of ruby is installed and run):

sudo gem install travis

Login with your github account by running:

travis login --org

On your remote host make sur that Docker engine and docker-compose are installed properly. Alternatively you can also install compose locally, this will be useful if you later add services on which you applicaiton rely (e.g: databases, reverse-proxies, load-balancer, etc ...).

Also, make sure that the user your logging in with is added to the docker group, this can be done on the remote host with:

sudo gpasswd -a ${USER} docker

This require a logout to be effective.

Building and pushing the image with Travis

In this first step you will be modifying your existing Travis workflow in order to push on the Hub the image we've built and tested. To do so, Travis will need to access the hub with your account. Let's add an encrypted version of your credentials to your .travis.yml file with:

travis  encrypt DOCKER_HUB_EMAIL=<email> --add
travis  encrypt DOCKER_HUB_USERNAME=<username> --add
travis  encrypt DOCKER_HUB_PASSWORD=<password> --add

We can now leverage the tag and push features of the Docker engine by simply adding the following lines to the script part:

- docker tag flask-demo-app:latest $DOCKER_HUB_USERNAME/flask-demo-app:production
- docker push $DOCKER_HUB_USERNAME/flask-demo-app:production

This will create an image tagged "production" and ready to download from your Docker Hub account.

Now, let's move on to the deployment part.

Automatic deployment with Travis

We will use Docker compose to specify how and which image should be deployed on your remote host. Create a docker-compose.yml file at the root of your project containing the following text:

version: '2'
services:
  app:
    image: <your_docker_hub_id>/<your_project_name>:production
    ports: 
      - 80:80

Send this file to your production host with scp:

scp docker-compose.yml ubuntu@host:

Now that you have set up docker-compose on your remote host, let's see together how you can prepare Travis to do the same automatically each time your application is builded and tested successfully.

The general idea is that you will add some build commands in the Travis instructions that will connect to your remote host via ssh and run docker-compose to update your application to its latest available version.

For that purpose, you will create a special ssh key that will be used only by Travis. The user using this key will be allowed to run only one script named deploy.sh which calls several docker-compose commands in a row.

Create a deploy.sh file with the following content:

docker-compose down
docker-compose pull
docker-compose up -d

Make the file executable and send it to your host with:

chmod +x ./deploy.sh
scp deploy.sh ubuntu@host:

Create the deploy key in your repo code with:

ssh-keygen -f deploy_key

Copy the output of the following command in your clipboard:

echo "command=./deploy.sh",no-port-forwarding,no-agent-forwarding,no-pty $(cat ./deploy_key.pub) 

Connect to your host and paste this output to the .ssh/authorized_keys of your user. You should end up with a command similar to this one:

echo 'command="./deploy.sh",no-port-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC/OAw[...]kQ728t3jxPPiFX' >> ~/.ssh/authorized_keys

This will make sure the only command allowed for the user connecting with the deploy key is our deployment script.

You can test that everything is in order by running once:

ssh -i deploy_key <your_user>@$<your_remote_host_ip> ./deploy.sh

Now that you have tested your deployment script, let's see how you can have Travis run it each time the tests are successful.

First let's encrypt the deployment key (necessary because you DO NOT want any unencrypted private key in your repository) with:

travis encrypt-file  ./deploy_key --add

Note the use of the --add option that will help you by adding the decryption command in your travis file. Refer to the Travis documentation on encryption to know more.

Add it to the project with:

git add deploy_key.enc
git commit -m "Adding enrypted deploy key" deploy_key.enc
git push

You should now be able to see your encrypted deploy_key in your projects settings on Travis-ci:

encrypted deploy key

Finally, add the following section to your .travis.yml file, (take care of updating accordingly your remote host ip):

deploy:
  provider: script
  skip_cleanup: true
  script: chmod 600 deploy_key && ssh -o StrictHostKeyChecking=no -i deploy_key ubuntu@<your_remote_host_ip> ./deploy.sh
  on:
    branch: master

Commit and push your change:

git commit -m "Added deployment instructions" .travis.yml
git push

Head to your Travic-ci dashboard to monitor your build, you should see a build output similar to this one:

deployment

Your build has been deployed to your remote host! You can also verify this by running a docker ps on your host and check for the STATUS column which should give you the uptime of the app container:

ubuntu@demo-flask:~$ docker ps
CONTAINER ID        IMAGE                    COMMAND             CREATED             STATUS              PORTS                NAMES
ae1797d92bf8        lalu/flask-demo:latest   "/entrypoint.sh"    3 hours ago         Up 10 minutes       0.0.0.0:80->80/tcp   ubuntu_app_1

To sum it up, here is what your final travis.yml file should look like:

sudo: required
language: python
services:
- docker
before_install:
- docker login --email=$DOCKER_HUB_EMAIL --username=$DOCKER_HUB_USERNAME --password=$DOCKER_HUB_PASSWORD
- openssl aes-256-cbc -K $encrypted_ced0c438de4d_key -iv $encrypted_ced0c438de4d_iv
  -in deploy_key.enc -out ./deploy_key -d
- docker build -t flask-demo-app .
- docker run -d --name app flask-demo-app
- docker ps -a
script:
- docker exec app python -m unittest discover
- docker tag flask-demo-app:latest $DOCKER_HUB_USERNAME/flask-demo-app:production
- docker push $DOCKER_HUB_USERNAME/flask-demo-app:production
after_script:
- docker rm -f app
deploy:
  provider: script
  skip_cleanup: true
  script: chmod 600 deploy_key && ssh -o StrictHostKeyChecking=no -i ./deploy_key
    ubuntu@demo-flask.buffenoir.tech './deploy.sh'
  on:
    branch: master
env:
  global:
  - secure: DCNxizK[...]pygQ=
  - secure: cnpkOl9[...]dHKc=
  - secure: wy5+mu0[...]MqvQ=

Et voila ! Each time you will be adding code to your repository that pass your set of tests, it will also be deployed to your production host ...

## Conclusion:

Of course the example application showcased in this series is very minimalistic. But it should be easy to modify the compose file to add, for example, some databases and proxies. Also the security could be greatly improved by using private installation of Travis. Last by not least, the workflow should be customized to support different branches and tags according to your enviromenments (dev, staging, production ...).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment