I ran into a problem reviving an old application from a few years ago. I want to install it on my server, but the version of Rails is so old that it won’t run on any sane version of Ruby to develop against. Docker to the rescue…
- Hardware: Core i5, 8GB RAM. Irrelevant anyway within reason. This probably won’t work on a RaspberryPi.
- OS: Ubuntu 17.04 LTS (zesty)
- Needed Ruby: 2.3.3 (originally 2.0.0; more later)
- Needed Rails: 22.214.171.124 (originally 3.2.8; more on that later)
This all started with teaching my son some supplemental lessons on nutrition. I got down into breaking up his meals into their components and analyzing them, etc – on paper. Then I remembered, back in 2013 I wrote a Rails app that did exactly this. So I grabbed it from my git repo and tried to fire it up on one of my servers…to be greeted with “Something went wrong”.
Nothing worked. The only version of Ruby I had on my server wouldn’t start up my app without major changes. I didn’t want to commit hours upon hours of development refitting an app I’ve lost interest in maintaining. Now, if I could solve this in a more generalized way…
Docker. Look, I know you’re all thinking, “why not use RVM or rbenv on your server? you’re wasting your time!”. I’ve done it that way. I have servers with complete build toolchains that don’t need them; simply so ruby-build can make and supply the needed Ruby version. These solutions work perfectly, on a development machine. On a server, it’s a headache, and it complicates deployments.
I already use Docker quite often to deploy other types of applications and servers. I like it. It’s easy. I can copy/paste/modify configs all over my system and deploy a new service in minutes. Sure, the same applies when rbenv is set up properly, but I’m talking bare linux with docker.
This didn’t happen right upfront for me. I had to solve all these issues while trying to get the later steps done. I’m putting it here to save you hours of re-doing and tweaking things.
You need to find a version of Ruby that will work with your application. In retrospect, I would do this with rbenv on my development box. Once I find a version that works with the app, that’s the version I’ll use in the Dockerfile. In my case, I had to upgrade both the rails version, and the ruby version, fixing bugs along the way, because I did things in a dumb way. In reality, I probably could have gotten away with not messing with the rails version or the ruby version by selecting the correct ruby image.
Step 1 – Development Machine
Install Docker if you haven’t already.
The Dockerfile. This goes in the root of the rails application. Where /app is.
You also want to create a .dockerignore file with the following contents:
.git log/* tmp/* db/*.sqlite3
The Dockerfile is just a set of instructions for building your rails app into a docker image. Once the Dockerfile is in place, you need to build the image. From the root of your rails app, run
docker build -t the_image_name_you_want .
Once the docker image is built, test it locally. Start up the docker container:
docker run --rm -d -p 3000:3000 --mount source=the_volume_name_you_want,target=/app_root/db the_image_name_you_want
Browse to localhost:3000 and see if your app works. If it doesn’t, attach to the docker container and peek at the logs.
Step 2 – Any Machine
Install Docker if you haven’t already. Now we have a choice – the easiest way to deploy a docker image is to use a registry. Docker Hub is a registry, but as this is an internal, private app, I’ll need my own registry. Fortunately, docker provides a registry – in a docker image! Let’s start him up on the production machine. Alternatively, if you use docker a lot already, you may already have a registry on your local network. In that case, skip this next part and substitute your own registry information.
To set up the registry:
docker run -d --rm -p 5000:5000 --name registry registry:2
That’s all there is to it. It won’t restart at boot, and will stay alive after you log off. If you want to kill it,
docker stop registry
This will also remove the container, so you only need to remember the run command above.
There are other options as well; if this is a one-time deployment, using docker’s image save command and transferring the (admittedly large) image over another channel may prove simpler, or may be required if you’re in an air-gapped environment.
Step 3 – Development Machine
Now, back on our development box, we’re going to tag our image and push it to the registry:
docker tag the_image_you_want repository.address:5000/the_image_you_want docker push repository.address:5000/the_image_you_want
If you get errors about HTTP vs HTTPS, you need to add your registry server to the insecure_registry list. This is in the docker documentation. This is a dockerd configuration parameter.
Step 4 – Production Machine
Now that we’ve got the image in our registry, let’s run it on the production machine:
docker run -d -p 3000:3000 --mount source=the_volume_name_you_want,target=/app_root/db -t the_APP_name the_image_name_you_want
Note the absence of the –rm parameters on the docker run command. We don’t want to delete this afterward; we’re going to just leave it in place. The reason, if you use the –rm flag to docker run, when the container is disposed, the volumes are disposed, too. We don’t want this, so we’re going to run it, make sure it’s set up correctly, and the systemd scripts we’ll write later will only start and stop the service.
Speaking of systemd…
This file goes in /etc/systemd/system as the_app_name.service. Then, you can manage the service via systemd.
Now you can integrate the server into your environment any way you like via your HTTPd or LB.
I’m leaving this space for any addenda I come up with.
I hope that answers a lot of questions. I pored over hours of documentation and experimentation to get this to work. Leave me a comment if you found this helpful, or have any suggestions.