Docker for Mac: Overcoming Slow Mounted Volumes with Visual Studio Code

A couple of years ago, Will Pleasant-Ryan wrote Docker for Mac: Overcoming Slow Mounted Volumes, describing his desire to use Docker for local development. He talked about some of the filesystem performance problems that can arise when using a shared volume from the host machine on Mac OS X, along with some potential workarounds and word of some upcoming performance improvements.

In the time since that post was written, those improvements have been released. But I’ve still found it to be unacceptably slow to have the source code mounted from the host machine, at least for something like a Ruby on Rails application.

However, earlier this summer, a Visual Studio Code update introduced the ability to “attach to a running container.” Once attached, the experience when editing files that reside on a container using VS Code is nearly indistinguishable from that of editing files on the local filesystem with VS Code!

1. Docker Compose

I’m using Docker Compose to manage the containers that make up my development environment. There’s plenty of existing documentation on how to use Docker Compose, but I want to point out a couple of things that I’ve done because I’m using a container to host the source code I’m editing.

A named volume for source code

If you just checked out a repository into a normal directory inside the container, you’d have to be careful not to lose any changes that had not been committed and pushed whenever making changes to container (updating something in the Dockerfile, etc.). But if the source code is checked out into a named volume, the container can be re-built over and over again, with your source code being mounted exactly as it was before.

A long-running container

Similar to a database container, your “dev” container should stay running when you do a docker-compose up so you can attach to it from VS Code. With Docker Compose, you just need to set the tty option to true in your docker-compose.yml.

Here’s an example docker-compose.yml file that has a couple of named volumes, one for the source code directory and the other for the contents of the database. I’m using this for an old Ruby on Rails project, hence the old version of MySQL.


version: "3"
services:
  mysql:
    image: xqdocker/ubuntu-mysql-5.5
    volumes:
      - dbdata:/var/lib/mysql
    ports:
      - "3306:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=password
  dev:
    build: .
    tty: true
    ports:
      - '3000:3000'
    depends_on:
      - mysql
    volumes:
      - devdata:/app

volumes:
  devdata:
  dbdata:

2. Working in the Dev Container

The idea is that any developer could check out the repository on their laptop, run docker-compose up, and have a working development environment. But you’ll still need a little customization of the container once it’s been built.

Check out the source code

Once you have your container up and running (docker-compose up), you’ll need to check out your source code inside the container. You can’t do this as part of the Dockerfile because we want the repository to live in the mounted volume.

If you rely on your SSH keys to authenticate with your Git server, then you’ll want to either mount your ~/.ssh directory in the container or copy your private key into the container manually. I needed to do the latter because my ~/.ssh/config has some things that aren’t compatible with the Ubuntu version running in the container.

These examples assume the container will be run as a user called “dev.”


> docker cp ~/.ssh/id_rsa project_dev_1:/home/dev/.ssh
> docker-compose exec -u root app chown dev:dev /home/dev/.ssh/id_rsa

I also like to have my normal Git configuration available as well:


> docker cp ~/.gitconfig project_dev_1:/home/dev

Now you just check out your source code, but inside the container instead of on your host machine.


> docker-compose exec app git clone [email protected]:some-org/some-project.git .

At this point, you can open a shell in the container and run tests, start the development server, etc.


> docker-compose exec app /bin/bash -l
$ bundle exec rspec spec/models/whatever_spec.rb

3. Editing in VS Code

With the “dev” container running, inside of Visual Studio Code, run the “Remote-Containers: Attach to Running Container” command and select your “dev” container. VS Code will attach and install everything it needs to run on the container.

If you select the File Explorer, you’ll see an “Open Folder” button that, when clicked, will display folders on the container. Select the location of the source code that was checked out previously, and click OK. You’ll now be able to edit files as if they were hosted on the local filesystem.

You can even open an integrated terminal in VS Code, and it will automatically open a shell in the attached container!

Conclusion

I’ve only recently started playing with hosting my entire development environment, including the source code, inside Docker containers, but the experience using VS Code as an editor has been fantastic.

With the Mac OS X filesystem integration performance improvements that have been made, many projects will be just fine using shared volumes mounted from the host system. But in cases where that just won’t cut it, hosting the source in the container and using VS Code to attach to the container is a really, really nice solution.