Dmytro Larkin

Docker for Development on MacOS

Development environment should be a sharp tool.

Using Homebrew for development environment configuration is boring. Development environment should be temporary and be reproducible whithin couple of minutes. Yes, it is about hosting dotfiles and writing scripts which do support binaries installation. For variety of versions. So modern solution is about using containers. There are many ways to use containers for development and their orchestration. What is a rapid solution for lazy devs? The Docker Desktop. Easy, install and run.

However, the Docker Desktop on MacOS is awful. It is slow. Still slow and greedy for a host machine power.

How to use it?

Well, after many tries, the best solution is to run docker engine on the linux virtual machine.

Why don’t use linux directly?

Well, that does not solve the variety of versions problem. You give up to use containers at the end as the most convinitent solution.

Install

Install Docker Desktop. Run it and then disable it. Don’t start the engine.

Install virtualbox binaries or use Homebrew. Yes, it already supports the macOS Monterey.

brew install --cask virtualbox
brew install --cask virtualbox-extension-pack

Install Vagrant binaries or use Homebrew

brew install vagrant
vagrant plugin install vagrant-vbguest

Done.

Setup

Keep your infrastracture as a code.

Create two files and track them with VCS. This is your vagrant working directory.

.
├── Vagrantfile
└── install.sh

a Vagrantfile should be something like this

Vagrant.configure("2") do |config|
  config.ssh.forward_agent = true

  config.vm.provider :virtualbox do |v|
    v.memory = 12288 # don't be greedy, it should be 40-70% of your host memory
    v.cpus = 4       # and this should be 60-80% of your real CPU cores
    v.gui = false
  end

  config.vm.define "docker" do |c|
    c.vm.box = "ubuntu/hirsute64"

    # Docker ports
    c.vm.network "forwarded_port", guest: 2375, host: 2375, id: "dockerd"

    # Application ports
    c.vm.network "forwarded_port", guest: 1313, host: 1313, id: "hugo"
    c.vm.network "forwarded_port", guest: 3000, host: 3000
    c.vm.network "forwarded_port", guest: 3001, host: 3001
    c.vm.network "forwarded_port", guest: 3002, host: 3002
    c.vm.network "forwarded_port", guest: 3306, host: 3306, id: "mysql"
    c.vm.network "forwarded_port", guest: 5432, host: 5432, id: "psql"
    c.vm.network "forwarded_port", guest: 6379, host: 6379, id: "redis"

    c.vm.provision :shell, privileged: false, binary: true, path: "install.sh"
  end
end

Modify resources allocation and application ports to make them available on the host.

The install.sh script automates docker engine provisioning

#!/usr/bin/env bash

echo '--- SYSTEM UPDATE ---'
sudo apt update
sudo apt install ca-certificates curl gnupg lsb-release net-tools

echo '--- INSTALL DOCKER ---'
curl -fsSL https://get.docker.com | sudo sh
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

echo '--- SETUP DOCKER SYSTEMD ---'
sudo mkdir /etc/systemd/system/docker.service.d/

cat <<EOF >>/tmp/docker.conf
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd
EOF
sudo mv /tmp/docker.conf /etc/systemd/system/docker.service.d/docker.conf

echo '--- SETUP DOCKERD ---'
cat <<EOF >>/tmp/daemon.json
{
  "hosts": [
    "tcp://0.0.0.0:2375",
    "unix:///var/run/docker.sock"
  ]
}
EOF
sudo mv /tmp/daemon.json /etc/docker/daemon.json

echo '--- RELOAD DOCKERD ---'
sudo systemctl daemon-reload
sudo systemctl restart docker

echo '--- SETUP DOCKER PERMISSIONS---'
sudo usermod -aG docker $USER

Done.

Run

Run vagrant and it will setup everything for you

vagrant up

Create a docker context.

docker context create vagrant --docker host=tcp://localhost:2375

Use right context and both docker and docker-compose will follow it.

docker context use vagrant

Here you go. Docker is ready. You may forget about docker engine while you work with your development stack.

Remote development supported by a variety of tools. Such as VSCode Devcontainers and JetBrains Remove Development. You may connect containers remotely using shell tools.

Stop

Pause, when you need to restore it. If you have persistent volumes they stay the same, unchanged. The regular routine. You may restart your host to get that state.

vagrant halt

DANGER! Destroy, when you don’t need it anymore. That normally does not happen. The outrage case. But that’s the most convenient and fast way to reset docker engine.

vagrant destroy --force

Details

Now you have a couple of docker contexts. Current is set to vagrant.

NAME         TYPE   DESCRIPTION   DOCKER ENDPOINT                 ...
default      moby   ...           unix:///var/run/docker.sock     ...
vagrant *    moby                 tcp://localhost:2375            ...

You may toggle between contexts. Use raw Docker Desktop when you need it. You may run more docker engines.

Docker follows context and shows you external engine resources.

docker info

Client:
 Context:    vagrant
 Debug Mode: false
 Plugins:
  buildx: Docker Buildx (Docker Inc., v0.7.1)
  compose: Docker Compose (Docker Inc., v2.2.1)
  scan: Docker Scan (Docker Inc., v0.14.0)

Server:
...
 Kernel Version: 5.11.0-41-generic
 Operating System: Ubuntu 21.04
 OSType: linux
 Architecture: x86_64
 CPUs: 4
 Total Memory: 11.69GiB
 Name: ubuntu-hirsute
...

Good enough. Cheers!

#docker #devops