Capistrano package as a DEB (for Ubuntu Precise) and as a RPM (for Centos 6). Capistrano is a CLI Ruby application with some gems as prerequisites.
First we need to build generic omnibus-enabled images for each distro we want to manage. Here are the Dockerfiles to use :
# Ubuntu precise Dockerfile (into omnibus-precise dir)
FROM ubuntu:precise
RUN apt-get update
RUN apt-get -y install -y vim git-core curl
RUN gpg --keyserver hkp://keys.gnupg.net --recv-keys D39DC0E3
RUN curl -sSL https://get.rvm.io | bash -s stable --ruby=2.1.5
RUN /usr/local/rvm/bin/rvm-shell -c "gem install omnibus"
RUN cd /root && git clone -b omnibus/3.2-stable https://github.com/opscode/omnibus-software.git && cd omnibus-software && /usr/local/rvm/bin/rvm-shell -c "gem build *.gemspec && gem install *.gem" && cd .. && rm -rf omnibus-software
WORKDIR /root
ENTRYPOINT ["/usr/local/rvm/bin/rvm-shell"]
# CentOS 6 Dockerfile (into omnibus-centos6 dir)
FROM centos:centos6
RUN yum install -y which tar yum-utils git rpm-build
RUN gpg2 --keyserver hkp://keys.gnupg.net --recv-keys D39DC0E3
RUN curl -sSL https://get.rvm.io | bash -s stable --ruby=2.1.5
RUN /usr/local/rvm/bin/rvm-shell -c "gem install omnibus"
RUN cd /root && git clone -b omnibus/3.2-stable https://github.com/opscode/omnibus-software.git && cd omnibus-software && /usr/local/rvm/bin/rvm-shell -c "gem build *.gemspec && gem install *.gem" && cd .. && rm -rf omnibus-software
WORKDIR /root
ENTRYPOINT ["/usr/local/rvm/bin/rvm-shell"]
We can now build our Docker images:
$ cd omnibus-precise $ docker build -t omnibus:precise . $ cd ../omnibus-centos6 $ docker build -t omnibus-centos6 . $ cd ..
At this point, we now have two ready-to-use images. The way they have been built (with rvm in this case) doesn't really care as soon as you have both the omnibus command line and the omnibus-software gem installed.
The omnibus-software gem we manually installed contains «recipes» to package some very common software stacks (PHP, Java, Ruby, Node, Python, RabbitMQ, Redis...).
We can now use the freshly built images to create an empty omnibus project. We simply mount a local volume to the container to keep the project from being destroyed with the temporary container.
$ mkdir project $ docker run --rm -ti -v $(pwd)/project:/root/project omnibus:precise root@743bc3900afb:~# cd project root@743bc3900afb:~/project# omnibus new capistrano create omnibus-capistrano/Gemfile create omnibus-capistrano/.gitignore create omnibus-capistrano/README.md create omnibus-capistrano/omnibus.rb create omnibus-capistrano/config/projects/capistrano.rb create omnibus-capistrano/config/software/c-example.rb create omnibus-capistrano/config/software/erlang-example.rb create omnibus-capistrano/config/software/ruby-example.rb [...] root@743bc3900afb:~/project#
We now have an omnibus structure to host our capistrano package project. We just have to write a few files to make this work. We can either do this within the current container or into the host.
# omnibus-capistrano/config/projects/capistrano.rb
name 'capistrano'
maintainer 'Arnaud'
homepage 'http://octo.com'
install_dir '/opt/capistrano'
build_version '3.3.3'
build_iteration 1
# creates required build directories
dependency 'preparation'
# capistrano dependencies/components
dependency 'capistrano'
# version manifest file
dependency 'version-manifest'
exclude '\.git*'
exclude 'bundler\/git'
# omnibus-capistrano/config/software/capistrano.rb
name "capistrano"
default_version "3.3.3"
dependency "ruby"
dependency "rubygems"
relative_path "capistrano"
build do
env = with_standard_compiler_flags(with_embedded_path)
gem "install capistrano" \
" --version '#{version}'" \
" --no-ri --no-rdoc" \
" --bindir '#{install_dir}/bin'", env: env
end
The dependency statement refers to compilation/installation recipes defined into omnibus-software that we reuse.
Your packages are ready to get built. However, you may want to add few more tweaks into postint and postrm scripts. In this example, we simply symlink cap to get it in the regular PATH.
#!/bin/bash
# omnibus-capistrano/package-scripts/capistrano/postinst
PROGNAME=$(basename $0)
function error_exit
{
echo "${PROGNAME}: ${1:-"Unknown Error"}" 1>&2
exit 1
}
ln -s /opt/capistrano/bin/cap /usr/bin || error_exit "Cannot link cap to /usr/bin"
exit 0
#!/bin/bash
# omnibus-capistrano/package-scripts/capistrano/postrm
rm /usr/bin/cap
exit 0
At this stage, you can build your packages:
$ cd omnibus-capistrano $ docker run --rm -v $(pwd):/root/project/ -w /root/project omnibus:precise -c "omnibus build capistrano" $ docker run --rm -v $(pwd):/root/project/ -w /root/project omnibus:centos6 -c "omnibus build capistrano"
It's going to take a while (several minutes) to recompile the whole bunch of softwares needed. The volatile Docker containers created will die at the end of their runs and leave a clean host.
After both runs, you will find your brand new packages ready to be deployed:
$ ls -lh pkg/ total 40M drwxrwxr-x 2 arno 4,0K 5 déc. 20:17 ./ drwxr-xr-x 6 arno 4,0K 8 déc. 18:05 ../ -rw-rw-r-- 1 root 21M 5 déc. 20:02 capistrano_3.3.3-1_amd64.deb -rw-rw-r-- 1 root 574 5 déc. 20:02 capistrano_3.3.3-1_amd64.deb.metadata.json -rw-rw-r-- 1 root 20M 5 déc. 20:17 capistrano-3.3.3-1.el6.x86_64.rpm -rw-rw-r-- 1 root 571 5 déc. 20:17 capistrano-3.3.3-1.el6.x86_64.rpm.metadata.json
Packages are pretty fat (around 20 megs).
This kind of approach is pretty interesting to keep the ugly / complicated building stuff confined into a volatile Docker container. At the end of the building process, the container is wiped and the hosting system remains clean. You therefore get a ready-to-go native distribution package with no further Internet access required.
On the Omnibus side, the main benefit of such a solution is that it addresses several distributions at the same time. You just have to build one Docker image per-distro to ensure the portability of your apps.
You can use pkgr exactly the same way. The main difference between thoses tools is that pkgr is faster but produces packages that have a few dependencies with system packages whereas omnibus embraces the no-dependencies paradigm.
On the Docker side you can begin to use it even if your organization is not yet ready to get it up to production.
You're now ready to integrate the building process into an CI tool such as Jenkins, add the produced packages to a repo (YUM, APT or even Nexus).