Skip to content

Tag: ansible

Deploying Docker Containers without Leaking Secrets

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 is 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_files:
- "vars/Production.yml"
tasks:
- name: Clone git repository
git:
repo: ssh://git@git.mycompany.com/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
docker_image:
name: myproject-prod
path: /opt/myproject/src
state: present
force: yes
buildargs:
settingsfile: "{{ settings_file }}"
when: gitpull.changed
register: build
- name: Run the container
docker_container:
name: myproject-prod
image: myproject-prod:latest
published_ports: '8000:8000'
state: started
restart_policy: always
recreate: '{{ "yes" if (build.changed) else "no" }}'
env:
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:

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.