AWS EC2 Managed Host Using Docker Machine

I've been playing around with docker for while now and have been impressed by how easy it was to manage, deploy my application for both development and production. Just last month that I found out how to use docker-machine to create, manage and deploy my rails application to Amazon EC2 instance. Configuring docker can be overwhelm at first, but I guarantee you will feel rewarded afterward. Lets get started.


Dockerfile for Rails App

The first step is to build a docker image for your rails app. I used ruby version 2.4.1 as a base image and from there I installed necessary dependencies needed to get a rails app up and runing and the rest is just a typical rails commands.

# Dockerfile
FROM ruby:2.4.1

RUN mkdir -p /app

RUN apt-get update && apt-get install -y libpq-dev nodejs apt-transport-https --no-install-recommends && rm -rf /var/lib/apt/lists/* && \
    curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
    echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
    apt-get update && apt-get install -y yarn

ENV RAILS_ENV=production
ENV SECRET_KEY_BASE=44dabb1d3fd9f65fa5e6040827a34eda5495b36b3d9a43c3d7e7eff73b2b55e832552f8da7c814e6ea6969128c67bb77020276dc06761ec42d04213c8f0bde5c

COPY ./Gemfile /app
COPY ./Gemfile.lock /app
RUN bundle install --without development test

COPY . /app
RUN bundle exec rake assets:precompile

CMD ["bundle", "exec", "rails", "server", "-b", ""]

Dockerfile for Nginx

Next is to build nginx image to act as reverse proxy to rails app. I'll show you content of nginx.conf later.

# Dockerfile.nginx
FROM nginx

RUN apt-get update -qq && apt-get -y install apache2-utils



RUN mkdir log

COPY public public/

COPY config/nginx.conf /tmp/nginx.conf

COPY config/nginx.conf /etc/nginx/conf.d/default.conf


CMD ["nginx", "-g", "daemon off;"]

Nginx configuration

And here is the content of nginx config file. Notice that I've refered to rails app using container name blog, which we will config later in docker-compose.yml file.

# config/nginx.conf
upstream blog_app {
  server blog:3000;

server {
  listen 80;
  server_name blog_app;
  root /app/public;

  try_files $uri/index.html $uri @blog_app;

  location / {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;

    proxy_pass http://blog_app;
    access_log /app/log/nginx.access.log;
    error_log /app/log/nginx.error.log;

  location ^~ /assets/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;

Wire Up Docker Compose

Ok now that we have everything up and running it's time to write a docker compose file. In this file I've create three services: blog, web and db which correspond to rails app, nginx server and database server respectively. As you can see I created a volume for a database server which will mount on data directory inside rails app. This is important because without this your application data won't survive after you rebuild your image.

# docker-compose.yml
version: '3'
    build: .
    command: bundle exec puma -C config/puma.rb
      - db
      - RAILS_ENV=production
      - '3000'
      context: .
      dockerfile: Dockerfile.nginx
      - blog
      - '80:80'
    image: postgres:9.5
      - POSTGRES_USER=blog
      - POSTGRES_DB=blog_production
      - POSTGRES_PASSWORD=secret
      - './data:/var/lib/postgresql/data'

Create & Deploy Application to AWS EC2

Now it's time to deploy our app. First we need to setup an amazon ec2 instance and we do that by using docker machine. But first thing first you need aws access_key_id and secret_access_key. Put your access key in ~/.aws/credentials like this


Run the following command to create an ec2 instance. Each option is self explanatory so I don't need to tell you what it does.

$ docker-machine create --driver amazonec2 --amazonec2-instance-type "t2.micro" \
--amazonec2-region "us-east-1"  --amazonec2-open-port 80 blog

After it completed. Confirm that it is acitve by issue the following command and you'll get something like this

$ dokcer-machine ls
NAME   ACTIVE   DRIVER      STATE     URL                        SWARM   DOCKER        ERRORS
blog   *        amazonec2   Running   tcp://           v18.03.0-ce

If you see a dash sign in ACTIVE column then that means your machine is not an active machine. If so then you can run the following command to make your machine an active one. Follow the instruction and verify it again using docker-machine ls command

$ docker-machine env blog
export DOCKER_HOST="tcp://"
export DOCKER_CERT_PATH="/Users/norin/.docker/machine/machines/blog"
# Run this command to configure your shell:
# eval $(docker-machine env blog)

Once it is active every command that you'll run from now on will be executed on your EC2 instance instead. You can now start build & run docker-compose like you did on your local machine. So to bring our application to life issued to following command and wait for it to finished. The -d flag used to tell compose to run in background instead of foreground process and that is what we want.

$ docker-compose up -d

Now go and verify that the application is working by visiting the IP address of the server. You can get the ip address by issuing the following command

$ docker-machine ip blog

Congratulation! Now you have your app up and runing on a cloud server that is managed by docker.

All Rights Reserved

Let's register a Viblo Account to get more interesting posts.