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!
Top comments (2)
Does it really support M1 CPU which means not Rosseta? thanks
@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.