Local docker compose configuration
I’ve been exposed to Docker in the past and decided to get my Rails 4 app running in Docker with Postgres on the backend and Nginx as a reverse proxy. I probably spent too much time trying to get the image size smaller than 876 MB (My app takes only 37 MB of that). So I’ll take some time to document how I got the application (Postgres, Rails, Nginx) working with Docker with the files and images I used.
I start with a Dockerfile at the Rails root. You’ll noticed my Dockerfiles don’t have a CMD or ENTRYPOINT command. I’m currently using docker-compose to manage the relationship between the containers (Postgres, Rails, Nginx) and it handles the command with parameters as well.
FROM centurylink/alpine-rails
MAINTAINER Scott Stephenson <macsteps@gmail.com>
ENV APP_HOME /usr/src/lnat_app
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME
COPY Gemfile $APP_HOME
COPY Gemfile.lock $APP_HOME
RUN bundle install
COPY . $APP_HOME
Using alpine-rails, the image is 867 MB. Using ruby:2.2, the image was 893 MB. Not a huge difference, except perhaps for larger teams, where having almost 1 GB files flying back and forth exhausts network resources and every little reduction in images makes a difference. I also tried putting rvm on debian:jessie (to avoid using Ruby 2.1, which probably wouldn’t have mattered in my case, but I persisted), which was still much larger than both ruby and alpine-rails.
I then created a nginx directory at Rails root. In this directory I put an nginx.conf file:
user www www;
worker_processes 1; ## Default: 1
error_log /var/log/nginx/error.log;
pid /var/log/nginx/nginx.pid;
worker_rlimit_nofile 8192;
daemon off; # so nginx startup doesn't exit causing docker container to stop/exit
events {
worker_connections 1024; ## Nginx complains if not set here.
}
http {
upstream the_app {
server <docker-machine-ip>:3000;
}
server {
listen 80;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://the_app;
}
}
}
Shutting off daemon mode is important, because Docker is looking to run programs in the foreground and most Linux services are designed to run in the background (daemon mode). The daemon mode can also be shutoff in the docker-compose.yml file instead, which I’ll discuss later. I didn’t setup an access log at this point, just the error.log, which will actually be a symbolic link to STDERR (default of nginx image).
Next I need a Dockerfile for the nginx image, also in ./nginx (not to confuse with the app’s Dockerfile at Rails root):
FROM nginx
MAINTAINER Scott Stephenson <macsteps@gmail.com>
RUN apt-get update -y && groupadd www && useradd -u 999 -g www www && \
chown -R www:www /etc/nginx && chown -R www:www /var/log/nginx
COPY nginx.conf /etc/nginx
Then I put a docker-compose.yml file also at the Rails root:
version: '2'
services:
db:
image: postgres:9.4.1
ports:
- "5432:5432"
volumes:
- .:/etc/postgresql
- .:/var/lib/postgresql
app:
build: .
command: bin/rails server --port 3000 --binding 0.0.0.0
ports:
- "3000:3000"
links:
- db
volumes:
- .:/usr/src/lnat_app
nginx:
build: ./nginx
command: service nginx start
ports:
- "80:80"
links:
- app
In this case, docker-compose handles the linking of the containers together (i.e. nginx -> app -> db). I run Postgres in development. Nginx and Rails are using their default ports, 80 and 3000, respectively. I’m able to use service nginx start, because I turned off daemon mode in nginx.conf. You could also leave daemon off; out of nginx.conf, and use command: nginx -g "daemon off". You might do this if you want the nginx.conf file to be more portable to environments outside of Docker.
With this in place, I just need to build, create/setup the database, and launch the containers.
Run from Rails root:
docker-compose build
You’ll see Docker working step-by-step to build the images. When complete, create and setup the database:
docker-compose run -e RAILS_ENV=development app rake db:create db:setup
Rake outputs the results of setting up the database, running migrations, and seeding with sample data. Time to launch the containers.
If you want to view the logs immediately, use:
docker-compose up
If you want to continue working, then launch in daemon mode:
docker-compose up -d
Point your browser to:
http://<docker-machine-ip
If you’re on a Mac and using the Docker Toolbox, just type:
docker-machine ip
If you get any errors regarding find the ip, toss this into your ~./bash_profile and reload it:
eval "$(docker-machine env default)"
source ~/.bash_profile
That’s it for getting a development 3-tier application running with Docker Compose. There’s so much to do. Within the next couple weeks, I intend to try out Docker Swarm and perhaps use more ENVIRONMENT variables, with docker-gen to get around the messy