I’m ready for my first experiment with Visual Studio Code’s Remote-Container extension, and a more pleasant container-based Rails development environment. As mentioned in part one of this series, I won’t be building out a fully functioning environment in a single article, but rather, slowly build one up incrementally.
By the time I’m through with this particular experiment, I want my Rails app to boot inside the development container, both via the Rails console and as a server prcoess, and be accessible via a browser on the host computer. There will still be a long way to go, but booting the app seems like good, demonstrable progress.
Let’s go!
Even though the Remote-Container plugin offers the option to use existing Docker configurations that might be present in the application, I’m resisting the temptation to use that, and starting fresh. I want to lean on VS Code’s functionality as much as possible for this experiment, at least initially, so let’s run with the basic configuration files it provides for a devcontainer. Via the command palette, Select Add Development Container Configuration Files and select Ruby on Rails from the many available options. (It looks like the plugin is smart enough to narrow the choices for you automatically if it finds common Ruby files in the working directory.) Then, select the Ruby version your project uses. At this writing, I don’t see support for Ruby 3, but since I’m working with older apps anyway, that’s not a problem for me just yet.
This yields a new directory, .devcontainer, with two files in it. First is a simple Dockerfile:
There’s also a devcontainer.json file, which will help VS Code integrate with the new container:
For the rest of this experiment, and at least the immediate future, I want to see how far we can get by building off of these defaults.
Since this experiment targets an existing application, let’s first remove the default that explicitly adds Rails and Webdrivers from Dockerfile. We’ll install these based on the contents of the app’s Gemfile, just as we would in an old-school development context. Let’s not worry about the Node stuff and commented-out stuff; that’s for another time.
I also like the idea of renaming the devcontainer to something besides the default “Ruby on Rails” in devcontainer.json–maybe name it to match the application’s name, instead, or the name of its repository. VS Code shows this name in its UI, in the bottom left corner of the window, so make it something that’s useful, without being too verbose. Everything else in the file can stay the same for now.
Let’s see how far we’ve gotten to this point! Back in the command palette, select Reopen in Container to build the container. This can take a few moments, but once it’s done, we’ll hopefully see a new prompt in the VS Code terminal pane. Depending on your project’s name and the state of its Git repository, it’ll look something akin to this:
Typing ls
confirms the container has access to the application’s files. And thanks to Remote-Container, we’ve done this without typing docker-compose run
this and docker-compose run
that. I’m still deciding how I feel about that, but for now, it seems pretty neat. I like having to not type so much, if nothing else. Let’s keep running with it a little longer, and get some gems installed.
Did you know that for years, a stock Rails installation has included a basic bootstrapping script? In a lot of the legacy projects I pick up, though, it’s often never been touched since the project’s very first commit to version control. It’s actually been deleted in one or two. That’s too bad, because the smart people who continue to make Rails a thing put it there for a reason: It helps developers get to work on the project more quickly! I like the idea of letting Docker do what Docker does best, and letting Ruby do what Ruby does best. Maybe we can leverage the script for a nicer development experience.
By default, bin/setup installs gems, prepares databases, and does a tiny bit of housekeeping in the development environment. For now, let’s just worry about the installing gems part.
Run the rails bin/setup
script to install gem dependencies into the container. A quick rails -v
should show that the version of Rails indicated in your project’s Gemfile should now be installed!
I think we can do better, though. devcontainer.json has a configuration option for postCreateCommand
. What would happen if it were set to run bin/setup
automatically on container creation? Too controversial? Let’s find out. Make the change to devcontainer.json, and rebuild the container again.
Now, running rails -v
and gem list
should provide reasonable-looking output, based on the app’s Gemfile, without manually running bin/setup
first.
But will this thing boot?
What we’ve got so far should be enough to allow bin/rails console
to boot in the container. I say should because, for one app in particular, I had to build out a barebones .env file with some environment configurations. This was an artifact of the app’s existing development environment, which is atypical of anything I’ve seen otherwise in Rails. At any rate, give bin/rails console
a go. Does the app boot? Awesome! If not, your app may have something atypical of its own. Sorting that out by getting the console to boot can often be a lot more straightforward than going straight to the browser.
Before wrapping up this experiment, let’s test one more thing–can we see our container-based app in an actual browser? If you’ve done much work with Docker, you may be familiar with the concept of forwarding ports from your host computer to a container. We need to do that here–Remote-Container doesn’t set it up automatically, but it does guide us to where to make a small configuration change.
I want to set this in devcontainer.json, at least for now. I know conventional wisdom suggests doing this in Dockerfile (or docker-compose.yml), but the fact that Remote-Container suggests setting it in devcontainer.json is intriguing. So humor me, please; we may change it later.
Rebuild the devcontainer once more. Once it’s started, fire up the app with bin/rails server
in the VS Code terminal pane. Then hit http://localhost:3000 in your favorite browser, and it may just work! Sort of, anyway–if the app relies on the database or some other service we haven’t yet set up for its root page, you’ll likely see an error. But trust me, this is progress.
What good is building an onramp to ease future developers into working on your project, if they don’t know the onramp exists? During this process, I’ve been taking a few extra minutes to add a note to each application’s documentation (usually its README) to prompt them to check it out. I point out that it’s experimental, and invite collaboration to help improve it.
Let’s reflect for a moment: With three generated files and a few tweaks, the app’s building in a container and booting far enough that we can see it running in a console and a browser. We’ve learned that VS Code’s Remote-Container extension abstracts away a lot of boilerplate Docker setup, but not all of it. And we might be able to leverage bin/setup from Rails to finish the job, in a Ruby-like way.
For now, I think we’re at a good stopping point. Step away from the computer, go outside if you can, and reflect a bit on what we’ve accomplished and learned. See you in the next post, where we’ll try connecting to a database from the container.
Ruby on Rails news and tips, and other ideas and surprises from Aaron at Everyday Rails. Delivered to your inbox on no particular set schedule.