Tool to build and install Docker containers with Chef, Dockerfile and other provisioning tools.
Features:
- Config files are in Ruby.
- Manage complexity of running Docker containers for your environment in one place.
- Manage multiple containers
Other tools:
- docker-composer - with configs in yml
Docker-builder is similar to docker-compose but has some more functionality to customize installation of servers on the host.
Process of installing server in Docker container consists of the following stages:
Process of building and running container on the host machine:
- 
Build Docker image - it will create a Docker image on the host machine
- build using Dockerfile or Chef provisioning
 
- 
Run Docker container - provision host machine - run scripts locally on the host machine. It can be shell script of Chef recipe
- run container - docker run
- provision container - run script inside the container. It can be shell script of Chef recipe
 
- 
Install systemd service on the host machine to run Docker container automatically (optional) 
- 
Start/Stop container 
- 
Destroy container 
- 
Destroy image 
Concepts of running Docker containers:
- you can rerun containers without losing data. Data is stored on the host machine and shared with container.
Build Docker image:
- from Dockerfile
- Chef provisioning (machine_image)
Provision during installation container on the host machine by:
- running shell script inside container
- running Chef script inside container with Chef provisioning
- Install gem:
gem install docker-builder
We will build and run a simple Docker container with Nginx server.
- install gem
gem install docker-builder
- generate directory structure using generator
docker-builder generate --name=nginx --type=chef
it will create a folder nginx with necessary directory structure inside.
- in the folder edit config file config.rbwith common settings
common({
    'prefix' => "example-",
    'image_prefix' => 'example-',
    'dir_data' => '/disk3/data/my-examples/',
})
servers({
    'nginx'=>{
        # some server options here
    },
})
base({
})
- edit custom settings for the server in file servers/nginx/config.rb
add 'build', {
    "image_name" => "nginx",
    'build_type' => 'chef',
    "base_image" => {        "name" => "nginx",        "repository" => "nginx",        "tag" => "1.10"    },
}
add 'install', {
    "host" => {      'script_type' => 'chef_recipe',       'script' => 'install_host',    },
    "node" => {       'script_type' => 'chef_recipe',       'script' => 'install',    }
}
add 'docker', {
    "command"=> "nginx -g 'daemon off;'",
    'ports' => [
        [8080,80],
    ],
    'volumes' => [
        ['html', '/usr/share/nginx/html'],
        ['log/nginx', '/var/log/nginx/'],
    ],
    'links' => [    ]
}
add 'attributes', {
  'nginx' =>{
      "sitename" =>"mysite.local"
  },
}
- build Docker image
# from the folder with project
docker-builder build
- run container
docker-builder up
- check container is running
docker ps
# see container named example-nginx
- access container
docker exec -ti example-nginx /bin/bash
- access container from browser
http://localhost:8080
Process:
- 
Create container - docker create 
- 
setup network and other settings for container 
- 
run provision to setup host machine. Script is running on the host machine. 
{   
'provision'=>{
    'setup' => [
        {type: 'shell', ..}, 
        ..
    ]
    ...
}
- run provision to setup created (not running) container. Run script to copy/update files in container.
{   
'provision'=>{
   'setup'=> [
        {type: 'ruby', <<script_options>>}, 
        ..
    ]
    ...
}
- run container with docker run. Specify env variables, hostname and other options
- first provision of container - bootstrap script. Run script from inside running container only once. Script should be located inside container.
{   
'provision'=>{
   'bootstrap'=> [
        {type: 'chef', ..},
        ..
    ]
}
- provision to initialize container. Run script every time after container starts. Script should be located inside container.
{   
'provision'=>{
    'init'=> [
        {type: 'chef'},
        ..
    ]
}
- Use lock file to make sure the container does not start until the provision is finished.
- put scripts in /path/to/project/ <<server_name>> / scripts / install.sh
Process of building and running container on the host machine:
- 
Build Docker image - it will create a Docker image on the host machine
 
- 
Run Docker container - provision host machine - run scripts locally on the host machine (recipe install_host.rb)
- run container (docker run)
- provision container - run script in the container (recipe install.rb)
 
- 
Install systemd service to run Docker container (optional) 
- 
Start/Stop container 
- 
Destroy container 
- 
Destroy image 
- generate directory structure using generator
docker-builder generate --name=nginx --type=chef
it will create a folder nginx
- in the folder edit config file config.rbwith common settings
- edit custom settings for the server in file servers/nginx/config.rb
- build Docker image
# from the folder with project
docker-builder build
- run container
docker-builder up
- check container is running
docker ps
- access container from browser
http://localhost:8080
- Build docker image
cd /path/to/servers
docker-builder build -s server_name
- run docker container
cd /path/to/servers
docker-builder run -s server_name
it will run container.
access container:
docker exec -ti container_name /bin/bash
Run from outside container
'provision' => {
    "bootstrap" => [
        {'type' => 'shell', 'run_from'=>'host', 'script'=>'name=myserver ruby myprovision1.rb'     }
    ]
}
    
it will run script name=myserver ruby myprovision1.rb from the host machine.
- in config file
    'provision' => {
        "bootstrap" => [
            {'type' => 'chef', "script"=>"", "dir_base"=>"/opt/bootstrap", "recipe"=>"server::bootstrap" },
        ]
    },
it will run chef provisioning:
cd /opt/bootstrap/ && chef-client -z -j /opt/bootstrap/config.json --override-runlist "recipe[server::bootstrap]"
config file with attributes (/opt/bootstrap/config.json) for chef-client is generated automatically.
After checking out the repo, run bin/setup to install dependencies.
You can also run bin/console for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.
- edit config.rb in your root folder
You can put all settings in this config.rb file and/or use config.rb file in each server's folder.
Config files:
/path/to/project/config.rb
/path/to/project/servers/server1/config.rb
/path/to/project/servers/server2/config.rb
- CHEF_COOKBOOKS - list of paths to chef cookbooks
Build types:
- 'none' - no build required
- 'Dockerfile' - using Dockerfile and docker build command
- 'chef' - using Chef provisioning (gem chef-provisioning-docker)
- 'packer' - using Packer tool
- add additional paths for cookbooks
in folder with servers:
# /path/to/my/servers/.chef/knife.rb
cookbook_path cookbook_path+[
    '/path/to/my/cookbooks',
    '/path/to/my/other/cookbooks',
]
Example of building Docker container with Chef.
Assume that our server name is 'nginx'.
- edit config file 'myserver/config.rb'
####
- 
Chef recipes 
- 
cookbooks/nginx/recipes/build.rb place chef resources to be included in the Docker image 
- 
cookbooks/nginx/recipes/install.rb 
- 
cookbooks/nginx/recipes/install_host.rb 
- 
build 
# run from the folder
docker-builder build['nginx']
- shared data: /disk3/data/server-api/nginx-front
data for nginx server:
- 
/etc/nginx/conf.d 
- 
/var/www/html 
- 
/var/log/nginx 
- 
Main site - /var/www/html ==> /disk3/data/server-api/nginx-front/var/www/html 
- 
Config 
- config for server
'build' => {
      'build_type' => 'Dockerfile',
      "image_name" => "myname",
      "base_image" => {} # not used
  },
- config for server
'build' => {
      'build_type' => 'packer',
      "image_name" => "myname",
      "base_image" => {
        "name" => "nginx",        
        "repository" => "nginx",        
        "tag" => "1.10"
      },
      
      "packer" => { options for packer }
  },
- 
options for packer 
- 
cookbook_paths - list of paths 
- 
recipe_name 
- 
examples: 
- config for server
'build' => {
      'build_type' => 'none',
      "image_name" => "myname",
      "base_image" => {
          "name" => "mysql", 
          "repository" => "mysql", 
          "tag" => "3.4.9"
      },
  },
      
it will NOT build a new Docker image.
- run recipe install_host which runs on the host machine (not in container)
- run recipe install which runs from within the running container
docker-builder start -s server_name
it starts docker container which was previously created.
Process:
- Start docker container container with docker start ..
- Provision container
- packer - https://github.com/mitchellh/packer
Packer is a tool for creating machine images for multiple platforms from a single source configuration.
- 
run_extra_options- additional options for docker run command
- 
hostname 
{
..
servers({
    'zookeeper'=>{
    ...
        'docker'=> {
            ...
            'run_extra_options'=>'--hostname zookeeper'
        }
}
Sometimes you need to clear cache with server info in chef-zero server
docker-builder clear_cache
- commands
docker-builder :up_swarm
docker-builder :destroy_swarm
- config
docker: {
    # options here...
}
- swarm_network - network name
- swarm_options - options to pass to docker service create command
prefix for image names, container names, and service names (for swarm mode)
- prefix - common prefix. Added to all names
- container_prefix - prefix for containers
- image_prefix - prefix for images
- service_prefix - prefix for services
Example:
- container name = $prefix$container_prefix$name
prefix='my-'
container_prefix='test-'
container name will be like 
my-test-redis
- run script from the host
'provision' => {
    "setup" => [
        {  'type' => 'shell',     'script' => 'scripts/mysetup.sh',  },
     ]
},
- it will run the script
scripts/mysetup.sh
- first provision of container
- provision scripts run only once
- 
Dockerfile 
- 
include script /opt/bootstrap/bootstrap.sh in container 
ADD scripts/bootstrap.sh /opt/bootstrap/
RUN chmod +x /opt/bootstrap/bootstrap.sh
- config
'provision' => {
    "bootstrap" => [
        {  'type' => 'shell',     'script' => '/opt/bootstrap/bootstrap.sh',  },
     ]
},
docker-builder up -s server_name
Process:
- docker create with docker options
- entrypoint: /etc/bootstrap
 
- generate config with node attributes for chef and save it to temp/boostrap-server.json
- copy config file to container to /opt/bootstrap/config.json
- docker start
- when container starts it runs /etc/bootstrap which
- runs chef-client to provision server first time
 
- Docker container can be connected to multiple networks. Container has an IP in each network.
Docker networks can be created using docker command docker network create
Docker-builder allows you to manage networks for your container.
- connect to multiple networks and specify default gateway
define IP in each network.
it assumes that networks 'my_bridge1' and 'my_overlay1' exist.
'docker'=> {
..
'network': {
   default_gateway: '192.168.1.1',
   networks: {
      {net: 'bridge'}, # default docker bridge
      {net: 'my_bridge1', ip: '10.1.0.12'},
      {net: 'my_overlay1', ip: '51.1.0.15'},
   }
   
}
}
in this example container will be connected to three networks: * docker default bridge named 'bridge' * custom docker network named 'my_bridge1' with ip='10.1.0.12' * custom docker network named 'my_overlay1'
create networks:
docker network create --driver bridge --subnet=51.1.0.0/16 --gateway=51.1.0.1  my_bridge1
docker network create -d macvlan --subnet=10.1.0.0/16  --gateway=10.1.0.1 --ip-range=10.1.12.0/24 -o parent=eth0 my_overlay1
see docker networks:
docker network ls
- check
docker exec -ti mycontainer bash
ip route
# sample output
...
- Container will be connected to two networks and NOT connected to default Docker network 'bridge'
'docker'=> {
..
'network': {
   networks: {
      {net: 'bridge', action: 'remove'}, # remove default docker bridge
      {net: 'mybridge1', ip: '10.1.0.12'},
      {net: 'my_overlay1', ip: '51.1.0.15'},
   }
}
}