I maintain a Django application at the company at which I am employed. Initially, changes  weren’t very frequent, so logging into the host and running a few commands when the application needed to be updated wasn’t a big deal. However, feature creep has set in and changes to the code base are not  only more frequent, but are done by more people. Something had to be done.

Whatever solution I was going to implement had a few requirements:

  1. No secrets should be visible in source control.
  2. No secrets should be built into the Docker image (anyone who can inspect it can view said secrets).
  3. All code for the process should be stored in source control
  4. The deployment process should not be limited to being run from my laptop or the application server.

Numbers one and three are problematic together, as it’s very easy to  just leave a few files in your .gitignore that contain all of your  secrets, but that means that #4 is impossible then. Clearly, many people have done this before, right? I mean, isn’t this what CI/CD and DevOps are all about? So, I took to Google.

The third principal of the Twelve Factor App is that the “app stores config in environment variables.” I was already doing that: setting the environment variables for things like my database password and secret key in the supervisor config. A lot  of blog posts recommend setting those environment variables with the ENV command in the Dockerfile, but that violates requirement #2 and  possibly #1, depending on the method. So that was right out.

The docker run command supports passing environment variables to the  container with the -e flag. This seemed like the place to do it, but the question was how.

Another project I had worked on was doing a proof-of-concept of Ansible Tower. For a multitude of reasons (including very high licensing costs), we went with the open-source upstream, AWX, instead. As I’d been  implementing that, I’d become aware of Ansible Vault, the a feature of  Ansible that allows keeping sensitive data such as passwords or keys in  encrypted form instead of plaintext. As Ansible has modules for Docker, AWX was looking like a great tool to use for deployment.

I started writing a simple playbook:

- hosts: appservers
  become: yes
  become: appuser
    - "vars/Production.yml"
    - name: Clone git repository
        repo: ssh://[email protected]/myproject.git
        dest: /opt/myproject/src
        accept_hostkey: yes
        version: master
        key_file: /opt/myproject/.ssh/id_rsa
        force: yes
      register: gitpull
    - name: Build Docker Image
        name: myproject-prod
        path: /opt/myproject/src
        state: present
        force: yes
          settingsfile: "{{ settings_file }}"
      when: gitpull.changed
      register: build
    - name: Run the container
        name: myproject-prod
        image: myproject-prod:latest
        published_ports: '8000:8000'
        state: started
        restart_policy: always
        recreate: '{{ "yes" if (build.changed) else "no" }}'
            SECRET_KEY: "{{ secretkey }}"
            SQL_HOST: "{{ sqlhost }}"
            SQL_USERNAME: "{{ sqluser }}"
            SQL_PASSWORD: "{{ sqlpassword }}"
            BIND_PASSWORD: "{{ bindpassword }}"

I’m doing a number of things here. Most of them should be fairly simple,  but I’ll talk about a few of the more interesting things I did. First,  I’m only building the container when cloning the git repo reports a  change. There’s no need to rebuild the image if it’s going to build the  same thing. Second, I’m setting recreate to yes if I did rebuild the image, as I want to run the new container, not keep the old  one chugging along. I could have left it statically as yes and set the  task to run based on “when” conditions, but there are situations in which I may need to start the container via deployment (like if my host  goes down).

You’ll also note that I brought in a vars_file. This file contains  definitions of all of the variables you see in the “env” section of the  docker_container task and looks something like this:

secretkey: !vault |
sqlhost: prodsql.mycompany.com
sqluser: myproject_prod
sqlpassword: !vault |

This was created using the ansible-vault command, like this:

ansible-vault encrypt_string 'super_secret_password' --name 'sqlpassword'

This tool then asks you for the vault password, then spits out the encrypted version of the string.

From there, we can run the playbook like this:

ansible-playbook --ask-vault-pass -K -i prod deploy.yml

After entering our sudo and vault passwords, the application is deployed.

Next time, I’ll talk about turning this into a one-click operation with AWX.