Teaser Image

Allen Wei's Blog

Thoughts, stories and ideas.

continue to last blog http://allenwei.cn/setup-rails-development-environment-with-docker/

Beside postgresql, we may need redis and memcache, it will become complex to start development environment

We'll talk about how to simplfy setup local environment with fig

Install Fig

To install fig, you need run follow command

curl -L https://github.com/docker/fig/releases/download/1.0.1/fig-`uname -s`-`uname -m` > /usr/local/bin/fig; chmod +x /usr/local/bin/fig  

Config Fig

You need create fig.yml in root path of your Rails project

rails:  
  build: .
  ports:
    - "3000:3000"
  volumes:
    - ./:/usr/src/app
  links:
    - "postgresql:postgresql"
postgresql:  
  image: postgres
  environment:
    POSTGRES_PASSWORD: "mypassword"

You can see fig support all configuration provided by docker

By simply add links you can specify the dependended container of your rails project

Build your container

before you start the rails container, we need build the image again, because fig will give the container a different name.

fig build  

tips: if you need more than one image want to build, fig will build all of them, very handy.

Start Rails container

because we've configured postgresql dependency in fig.yml, we can start all containers together

fig up  

Now you can see postgresql started along with Rails

You may notice your database is missing, because we are using different postgresql container now.

to create your database container is very simple

fig run rails db:create  

then migrations

fig run rails db:migrate  

to restart rails

fig restart rails  

to run some command in container

fig run rails bash  

what's more run your test

fig run rails rspec  

Add more service

rails:  
  build: .
  ports:
    - "3000:3000"
  volumes:
    - ./:/usr/src/app
  links:
    - "postgresql:postgresql"
    - "redis:redis"
    - "memcache:memcache"
postgresql:  
  image: postgres
  environment:
    POSTGRES_PASSWORD: "mypassword"
redis:  
  image: redis
memcache:  
  image: sylvainlasnier/memcached

you can get redis/memecache configuration by show environment variables in your rails container

fig run rails env  

you may noticed fig started redis and memcache

in next blog I'll talked about setup production environment locally with Vagrant and Docker

If you don't have docker installed you can check out my last blog http://allenwei.cn/setup-docker-on-your-mac/

Build your first rails docker image

Assume you already have a rails git repo

First We need create a Dockerfile in your rails folder

This time we'll use base image for latest ruby version, you can see other version here https://registry.hub.docker.com/u/library/ruby/

RVM? no you don't need it, because each container is isolated, so you don't need version management tool any more.

Here is the Dockerfile

FROM ruby:onbuild

CMD rails s  

That's it, really simple right?

Before you start the container, we need build it first

docker build -t my-rails-app .  

How to setup postgresql

You still need database like mysql and postgresql, you don't need install it on your mac, just use Docker container

You can find postgresql docker image here https://registry.hub.docker.com/_/postgres/

To start the postgresql container

docker run --name my-postgresql -e POSTGRES_PASSWORD=mypassword -d postgres  

Next you need link to postgresql to your rails project
How the rails app connect to postgresql?

When you link postgresql container to your rails app container, all configuration for postgresql as ENV variable

You can check those configuration by run follow command

docker run --name my-rails-app --link my-postgresql:postgresql --rm my-rails-app env  

you can see environment variables like:

POSTGRESQL_PORT=tcp://172.17.0.43:5432  
POSTGRESQL_PORT_5432_TCP=tcp://172.17.0.43:5432  
POSTGRESQL_PORT_5432_TCP_ADDR=172.17.0.43  
POSTGRESQL_PORT_5432_TCP_PORT=5432  
POSTGRESQL_PORT_5432_TCP_PROTO=tcp  
POSTGRESQL_NAME=/nostalgic_newton/postgresql  
POSTGRESQL_ENV_POSTGRES_PASSWORD=mypassword  
POSTGRESQL_ENV_LANG=en_US.utf8  
POSTGRESQL_ENV_PG_MAJOR=9.3  
POSTGRESQL_ENV_PG_VERSION=9.3.5-1.pgdg70+1  
POSTGRESQL_ENV_PGDATA=/var/lib/postgresql/data  

When you know the postgresql environment, you can edit your rails database config in config/database.yml

default: &default  
  adapter: postgresql
  pool: 5
  username: postgres
  password: <%= ENV['POSTGRESQL_ENV_POSTGRES_PASSWORD'] %>
  host: <%= ENV['POSTGRESQL_PORT_5432_TCP_ADDR'] %>
  port: <%= ENV['POSTGRESQL_PORT_5432_TCP_PORT'] %>

development:  
  <<: *default
  database: myapp_development

test:  
  <<: *default
  database: myapp_test

production:  
  <<: *default
  database: myapp_production

if you didn't add pg gem into your Gemfile, you need added gem 'pg' into you your Gemfile and rebuild your docker image

let's try out our pg configuration

    docker run --link my-postgresql:postgresql -it  my-rails-app rake db:create

OK, we've setup our environment, let's start to change our code.

I still ssh to my docker container? no, you edit your code on your mac directly, let's see how we do it.

We can mount your local folder on docker container, let's mount current rails folder to docker container

docker run --link my-postgresql:postgresql -it -v $(pwd):/usr/src/app my-rails-app bundle install

you can see when we run bundle install again, it is very fast, because the installed gem is already in the docker image

to access your rails server, you still need one more step, port docker port to you local machine

docker run --link my-postgresql:postgresql -it -v $(pwd):/usr/src/app -p 3000:3000 my-rails-app rails s

Because on Mac, we can only access docker container through boot2docker, we can run boot2docker ip to get the ip address

there is a convienent way to do it

open http://$(boot2docker ip):3000  

you can give a name to your container and run it as daemon

docker run --name=my-rails-app --link my-postgresql:postgresql -it -v $(pwd):/usr/src/app -p 3000:3000 -d my-rails-app rails s

So when you need restart your server, just restart the container by it's name

docker restart my-rails-app  

How to run test

There is two ways:

1) start new container to test your test. It possible since start a new container is super faster

docker run --link my-postgresql:postgresql -it -v $(pwd):/usr/src/app my-rails-app rspec

2) You can log in to your started docker container

docker exec -it my-rails-app bash

in next blog http://allenwei.cn/docker-dependencies-with-fig/, I'll introduce a way to simplfy the flow

There is no native docker support yet, you need virtualbox and boot2docker

Install virtualbox

You can find virtualbox binaries file here: https://www.virtualbox.org/wiki/Downloads

Install boot2docker

You can find boot2docker binaries file here: https://github.com/boot2docker/osx-installer/releases

To start boot2docker you need

init boot2docker environment

$ boot2docker init

bash

Start boot2docker virtualbox

$ boot2docker up

Test

You can test docker by run a smallest docker image busybox

On your Mac termnial

You can run

docker run busybox echo "hello word"  

SSH into your boot2docker vm

boot2docker ssh  

get your boot2docker vm ip address

boot2docker ip  

Upgrade boot2docker

boot2docker stop  
boot2docker delete  
boot2docker download  
boot2docker init  
boot2docker up  

Trouble Shooting

2014/11/30 10:28:50 Get http:///var/run/docker.sock/v1.15/containers/json: dial unix /var/run/docker.sock: no such file or directory  

Possible reasons:

  • You didn't start boot2docker, you can try boot2docker up
  • boot2docker environment didn't set, you can try $(boot2docker shellinit)
  • To be convienent you can put $(boot2docker shellinit) in your bash.rc or zsh.rc

Sometimes we need extra logic from code using Objective-C Category, but Category doesn’t support adding attributes.
Objective-C Associative References is a recure.

here is an example

@interface AFHTTPClient (Logging)

@property (readonly, nonatomic) AFHTTPClientLogger *logger;

@end
#import "<objc/runtime.h>"

@implementation AFHTTPClient (Logging)

static char AFHTTPClientLoggerObject;

- (AFHTTPClientLogger *)logger {
    AFHTTPClientLogger *logger = objc_getAssociatedObject(self, &AFHTTPClientLoggerObject);
    if (logger == nil) {
        logger = [[AFHTTPClientLogger alloc] initWithBaseURL:self.baseURL];
        objc_setAssociatedObject(self, &AFHTTPClientLoggerObject, logger, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }

    return logger;
}

@end

You can follow this steps:

  • define property in header file
  • add #import "<objc/runtime.h>" at top of implement file
  • define a getter method in implement file using objc_getAssociatedObject
  • define a setter method in implement file using objc_setAssociatedObject