Using Docker for Development with Middleman
Over the last 3 years I’ve picked up and played with Docker a handful of times. The idea and promises behind what it offers always excite me but every time I experimented with it I would get to a certain point, begin to hit weirdness, call it quits and decide I’d be better off spending the time sharpening other skills.
Well, it’s that time of the year! I decided to spend a bit of time with Docker again (using Docker for Mac) and set up a little contained development environment for working with this site, which is built with Middleman.
Here’s How it Went
I initially updated Docker via Brew but when you take that direction it’s still on you to set-up a Linux VM in VirtualBox and connect through Docker Machine (I think). That’s a pain and Docker recommends Docker for Mac. So, I got that up and running.
Next I added a Dockerfile
to the root directory of my Middleman project:
/Users/rbosinger/Projects/Personal/ryanbosinger_com_site/
▸ .git/
▸ build/
▸ source/
.gitignore
.ruby-version
config.rb
Dockerfile
Gemfile
Gemfile.lock
README.md
My Dockerfile
looks like this:
FROM ruby:2.3.4-onbuild
RUN bundle install
CMD ["middleman", "server"]
We set a base image from DockerHub. This Ruby image is a hefty build and it’s certainly possible to build up your own, lighter, base image. I’m not going to bother with that yet; I just want to get rolling. One thing to note about this image is that it will assume your current directory is your Ruby app and copy your gemfile into the image for you. In other blog posts which may describe a more manual process you may see commands like
ADD Gemfile
andADD Gemfile.lock
. You can see the Dockerfile for this image here.We run
bundle install
to get things set-up.Run the command
middleman server
to get started right away.
That’s it! Now we can build an image from this Dockerfile like so:
sudo docker build -t rbosinger/ryanbosinger_com .
Here I’m using the -t
option to give this image a name. This makes referencing it easier. Make sure to note the .
at the end. This tells Docker that the Dockerfile is to be found in the current directory.
Running this command should give you output similar to this:
Step 1/3 : FROM ruby:2.3.4-onbuild
# Executing 4 build triggers...
Step 1/1 : COPY Gemfile /usr/src/app/
---> Using cache
Step 1/1 : COPY Gemfile.lock /usr/src/app/
---> Using cache
Step 1/1 : RUN bundle install
---> Using cache
Step 1/1 : COPY . /usr/src/app
---> e4cd6c0d2201
Removing intermediate container 17d6da3b0a45
Step 2/3 : RUN bundle install
---> Running in afc30abd0080
Using concurrent-ruby 1.0.5
Using i18n 0.7.0
...
Bundle complete! 14 Gemfile dependencies, 68 gems now installed.
---> 92cc6846f71b
Removing intermediate container afc30abd0080
Step 3/3 : CMD middleman server
---> Running in 2d73b17c9cf7
---> e0f2e0d76e37
Removing intermediate container 2d73b17c9cf7
Successfully built e0f2e0d76e37
Successfully tagged rbosinger/ryanbosinger_com:latest
Perfect. Now if we run docker images
we should be able to see our newly created image:
REPOSITORY TAG IMAGE ID CREATED SIZE
rbosinger/ryanbosinger_com latest e0f2e0d76e37 6 minutes ago 920MB
We have our image and now it’s time to run it in order to use it.
docker run -p 4567:4567 -p 1234:1234 -v /Users/rbosinger/Projects/Personal/ryanbosinger_com_site:/usr/src/app rbosinger/ryanbosinger_com
What’s going on here?
We use the
-p
flag (for “port”) to specify port mappings between the container and our host. The Middleman server runs on port 4567 by default so we want to open that one. I’m also opening port 1234 for LiveReload (this is not the default port but I’ll explain further on).We use the
-v
flag (for “volume”) to mount our project directory into the Docker container. Without doing this we would have to rebuild our Docker image every time we changed a file. Note that I’m using my full local path to my Middleman project. I’m mapping that to/usr/src/app
which was set-up by the Ruby image we inherited from.Finally, I specify the name of the image I want to run (
rbosinger/ryanbosinger_com_site
)
If all goes well you should see something like this:
== The Middleman is loading
== Blog Sources: /blog/{year}-{month}-{day}-{title}.html (:prefix + :sources)
== LiveReload accepting connections from ws://0.0.0.0:1234
== View your site at "http://localhost:4567", "http://127.0.0.1:4567"
== Inspect your site configuration at "http://localhost:4567/__middleman", "http://127.0.0.1:4567/__middleman"
We mapped port 4567 so pointing a browser to http://127.0.0.1:4567/
should load up the site. Nice.
Ok, why did you change the LiveReload port?
LiveReload, by default, uses port 35729. I tried many things but simply could not get my localhost to properly establish a websocket connection when using this port. In the end I made these changes and got it working. You may have different luck.
config.rb:
configure :development do
activate :livereload, :host => '0.0.0.0', :port => '1234'
end
A Few Tips
Shutting down the server
It’s the same as always! Just hit CTRL-C
to shut down the process. Run the docker run
command again to bring it back up.
Checking things out
You can create another container from this image that runs a Bash shell in order to check things out.
~ docker run -i --rm -t rbosinger/ryanbosinger_com /bin/bash
root@75399157c8b8:/usr/src/app# ls
Dockerfile Gemfile Gemfile.lock README.md build config.rb source
If you list your containers you can see we now have two active containers built from our image:
CONTAINER ID IMAGE COMMAND CREATED STATUS
75399157c8b8 rbosinger/ryanbosinger_com "/bin/bash" 6 seconds ago Up 5 seconds
ccaa50968ee1 rbosinger/ryanbosinger_com "middleman server" About a minute ago Up About a minute
Here I am checking some versions within the container:
root@8fe4edd10b66:/usr/src/app# ruby -v
ruby 2.3.4p301 (2017-03-30 revision 58214) [x86_64-linux]
root@8fe4edd10b66:/usr/src/app# middleman version
Middleman 4.2.1
Pruning
When you run docker ps -a
you’ll get a list of all containers, including ones that have already been exited/shutdown. You can clean this up by running:
docker system prune
Warning: I mention this assuming you’re playing with Docker on your own machine and don’t have any other important Docker-related stuff going on. Don’t just go running that anywhere.
Cheers,
Ryan