In this post I’m going to show you how use
ruby-install to build a
Debian package of MRI itself, and put together a trivial apt
repository so you can serve it on your local network. Why do this?
First, you aren’t dependent on ftp.ruby-lang.org being available when
you want to deploy. Second, that you don’t waste time during your
deployment rebuilding a ruby binary. I’ve seen builds of ruby 2.0.0
take 10 minutes or so, and it’s no fun waiting for that when you’ve
got an urgent redeploy waiting.
You should note that the packages we’ll build here are the simplest possible packages to get a ruby binary installed on a Debian system. They are the base minimum required to get that one job done. They bear only a passing resemblance to the sort of quality of package you’ll find either in Debian itself, or in any of the other public repositories offering Ruby packages (like John Leach’s excellent Ubuntu packages, over here). You should use these strictly internally, since we cut several corners to build the simplest packages that can possibly work.
As before, I’m assuming you’re building on and deploying to Debian
Stable, but the same instructions should work on Ubuntu. You’ll
installed, along with the
Here’s the script which does the bulk of the work. I’d suggest
pasting this into an executable file called
somewhere handy on your
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
You run it like this, specifying the MRI version you want to package on the command-line:
This will build a
.deb file at
ruby-install-deb/ruby-install-ruby2.0.0p195_1_amd64.deb. If you just want to
see how to host this package inside your network and don’t want to
bother with the details of how the above script works, skip to “Building
a repository” below.
Broken down into its component parts, there isn’t much to the script
above. We make a directory to build ruby in, construct a
which knows how to use
ruby-install, strap in the few parts that
define a Debian package using that
Makefile, and finally add the
I’ll run through the individual parts to highlight some of the details.
1: Top-level sequence
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
Hopefully this is self-explanatory. It’s a literal transliteration of the previous paragraph into Ruby. The one thing worth noting here is that both the directory name and the Makefile depend on the ruby version we’re building. This is due to the vagaries of Debian packaging, which I’ll point out more of as we go along.
2: Directory Structure
1 2 3 4 5 6 7 8 9 10 11 12
This might look a little odd, but it’s mostly dictated by how building a Debian package works. First, we create a top-level directory to do all our work in. The name of this directory isn’t important.
Next, we construct a nested directory where we’re going to keep the actual package metadata. The name of this directory is important, since the Debian tooling is going to use it to pick up both the package name and the package version. The scheme I’ve chosen here will give us packages which can, in principle, allow you to have more than one ruby version installed at once. Here’s an example.
Say we generate a package for ruby
2.0.0-p247. The name of the
working directory we’ll generate will be
ruby-install-ruby2.0.0p247-1. When we generate our debian package
metadata in a moment, the tool we use to do it will pick out
ruby-install-ruby2.0.0p247 as the package name, and
1 as the
package version. If we hadn’t dropped the hyphens with
instead gone for
ruby-install-ruby-2.0.0-p247, the package we
generated would be called
ruby-install-ruby, and the package version
2.0.0-p247. That would stop us from having more than one
ruby version installed at a time, but it would also complicate
deploying an updated package since as soon as we uploaded a later ruby
apt-get update on any host we’d previously installed to
would grab the later version.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
#makefile() method generates a Makefile which defines a
all task that calls ruby-install to build ruby. It also contains a
make install task to copy the built binaries into place.
When we generate the package metadata, it’ll rely on those two tasks.
The variables at the top of the generated
Makefile are worth looking
DESTDIR is used by the Debian package building system so the
install task doesn’t actually install the package we’re building to
the system we’re building it on.
DESTDIR is specified in the
GNU coding standards,
and all Debian package Makefiles should support it.
Note that we set a different
DESTDIR variable to pass to
ruby-install in the
root/$(RUBY_BIN) task. That
passed to ruby’s own
Makefile, so that it doesn’t have the location
it’s built at hardwired in.
I’m using the
RUBY_* variables to point at the location where
ruby-install will drop our compiled ruby binary, so that successive
make invocations won’t redo unnecessary work.
1 2 3 4 5 6 7
ruby-install helpfully includes a list of the system packages that
ruby depends on, in a format we can almost just drop into our Debian
package definition. The gotcha is that it lists build dependencies,
when we actually want runtime dependencies. Since we don’t want a
compiler and associated gubbins installed when we
our shiny new ruby package, we have to drop the
dependency. Fortunately for us, in the case of all the other
dependencies, the runtime dependency is listed as a dependency of the
build dependency so that, for instance, where
libreadline-dev will depend on
There is a small catch to listing dependencies like this. Once we’ve
built the packages, they have to be installed with
--no-install-recommends. This is because one of the
g++, which we’re trying not to include. If you prefer
not to rely on
--no-install-recommends, you could replace the first
two lines of
I don’t like hardcoded lists like that, but it’s a workable approach.
The next step is to generate the actual package metadata to turn our humble Makefile into a buildable package.
The files involved are a little fiddly, so we cheat. All we want is a package that’s buildable, rather than one that we could hope to submit upstream, so we can lightly abuse the Debian packaging toolkit to make our lives easier.
1 2 3 4 5 6 7
The critical line is the first, the rest is housekeeping.
is a rather nice little
debhelper tool - install it with
install dh-make - which exists to set up the boilerplate you need for
a Debian package.
It’s worth taking a look at the generated files. They’ll be in, for
changelog in specific are worth your time,
as they both contain fields you can edit to add useful cosmetic
information to the final package.
The remainder of this method is concerned with cleaning out a whole
load of example configuration files that
dh_make generates which we
simply don’t need.
I’ve got the above script saved to
~/bin/ruby-install-deb. I’m going to
build a package for ruby
2.0.0-p195. Here’s what I do, and the
1 2 3 4 5 6 7 8 9 10
You can safely ignore the
Please edit... message, since we know the
Makefile we’ve generated follows the rules.
1 2 3 4 5 6 7
dpkg-buildpackage command is what triggers our
-us -uc flags are to do with package signing, saying, in
short, don’t do it. This is another place packages for public
consumption would differ from these that we’re building here.
sudo, I need to authenticate; then after
a while, I get the output:
1 2 3 4 5 6 7 8 9
And it’s built! Now, it’s dropped packages in the directory above us, so let’s take a look at what it did:
1 2 3 4 5 6 7
.deb is the only file we strictly need.
Building a repository
Having packages is only half the battle. To make the packages easy to install at deploy-time, and to make dependency resolution work for us, we need to set up an apt repository. While we’re at it, we’ll build a couple more ruby packages, to see how they fit together.
Back in the our root directory, make a
rubies file to list which
ruby binaries we want to build. Mine looks like this:
1 2 3 4 5
Now, we can set up a debian directory for each of these like so:
1 2 3 4 5 6 7
So that’s given us three package definitions to play with. Here’s a
small script to build them all, which I’ve got saved to
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
This simply iterates over the debian directories it finds, and issues
dpkg-buildpackage for each one. Unfortunately these can’t run in
apt-get install and that’s
guaranteed to fail if you run it more than once at a time.
Nevertheless, once it’s finished, you can see what it has built:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
So now we’ve got a bunch of packages. We could stop here. If we did,
to install the packages we’ve just built, we’d need to copy the .deb
to our new host, and use a tool like
gdebi to install the .deb and
Let’s not do that. It’s better to put together an apt repository to act as a local mirror, and to let apt-get install our dependencies as normal.
To put together a minimal repository, all we need to do is gather our files together, build an index, and upload the files and index to a web server. The repository can be served by a purely static server, and doesn’t need any dynamic server support at all.
What we’re building here is what Debian calls a “trivial” repository. Technically they’re deprecated because they don’t support some apt features, but they’re fine for our purposes.
Here’s how we gather the files and build the index:
1 2 3 4
dpkg-scanpackages step will give you a couple of warnings. You
can safely ignore them.
I’ve explicitly ignored the
here. Again, they’re needed for specific apt features, and if all we
want to do is build a repository to let us install ruby onto a server
of our choice, we don’t need them.
All that’s left now is to upload the contents of
repo/ to a
convenient webserver. A fresh Debian stable VM with Apache or nginx
apt-get installed will do fine here:
1 2 3 4
Installing from the repository
Ok. So we’ve built packages, made a repository, and made it
available. Now we’re ready to actually use the package in
deployment. Assuming your new machine is called
new-vm, here’s how
you do that by hand.
1 2 3
At this point,
new-vm should know about the packages we’ve built.
1 2 3 4
If you don’t like having the filler info there as the package
description, you can edit it in
debian/control before building the
apt knows about the packages, and we should be able to
install them. We will be asked if they’re safe to install without
verification; since they (and the repository) are unsigned, just say
1 2 3 4 5 6 7 8 9 10 11
Hooray! For me, on a fresh local Wheezy VM with 256MB of RAM, apt-get installing a single ruby-install-ruby package, including dependencies, takes a minute. That’s better than compiling from source every time, by quite a long way.