DEV Community

Dmytro Larkin
Dmytro Larkin

Posted on • Originally published at dml.org.ua on

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
Enter fullscreen mode Exit fullscreen mode

Install Vagrant binaries or use Homebrew

brew install vagrant
vagrant plugin install vagrant-vbguest
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Done.

Run

Run vagrant and it will setup everything for you

vagrant up
Enter fullscreen mode Exit fullscreen mode

Create a docker context.

docker context create vagrant --docker host=tcp://localhost:2375
Enter fullscreen mode Exit fullscreen mode

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

docker context use vagrant
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 ...
Enter fullscreen mode Exit fullscreen mode

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
...
Enter fullscreen mode Exit fullscreen mode

Good enough. Cheers!

Discussion (2)

Collapse
zachary profile image
zachary

Does it really support M1 CPU which means not Rosseta? thanks

Collapse
dml profile image
Dmytro Larkin Author

@zachary, that's a good question!

It works if VitualBox runs on M1.

Luckily, you should not be dependent on VitualBox. You may use any of supported Vagrant providers. VMWare Play and Parallels work even better according to collective practice.

The setup above makes docker loosely coupled with a host. You can drop it any time to release plenty of resources, and reproduce within minutes again. You should not collect docker pieces across your system. People also use AWS EC2 or DO instances to host containers for dev. You switch contexts only. Easy. That is the goal for dev environment.

Docker Desktop on the Mac has slow IO, especially on Intel chips. So many tips and tricks to tune docker (e.g. Mutagen) but Linux beats everything, even on virtualization. I guess, I hope, native docker desktop works much faster on M1 Pro/Max with >4TB storage. Perhaps, doing docker system prune --all --volumes -f on that setup will be efficient enough.