Search code examples
macosdockermacos-sierra

How to start Docker for Mac daemon on boot?


I want to have Docker for Mac start on boot, not on login. How do I do this? I'd like to create a LaunchDaemon, I just don't know what program to start.

All the examples I see seem to use docker-machine to define a VM, but the documentation now says that docker-machine is now not the way to go if you want to use the default VM created when Docker is installed, as Docker for Mac doesn't use docker-machine to create the VM.

The reason is that I want to start Docker to run Jenkins and Nexus containers on boot, as the server is going to be used as a CI server. The Docker GUI has the preference option of starting on login, which is not the same thing as on boot.

I am running OSX Sierra.


Solution

  • TL;DR Running Docker for Mac as a standalone service is not supported.

    Instead run Docker in a VirtualBox VM, either by creating a plain VM or using docker-machine. Then set the Virtualbox VM up to run at boot.

    This is the LaunchAgent I use for Virtualbox:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
      <dict>
        <key>KeepAlive</key>
        <true/>
        <key>Label</key>
        <string>vm.dockervm</string>
        <key>ProgramArguments</key>
        <array>
          <string>/usr/local/bin/VBoxHeadless</string>
          <string>-s</string>
          <string>dockervm_default_1667177741</string>
        </array>
        <key>RunAtLoad</key>
        <true/>
        <key>UserName</key>
        <string>me</string>
        <key>WorkingDirectory</key>
        <string>/Users/me</string>
        <key>StandardErrorPath</key>
        <string>/Users/me/log/vm/dockervm.log</string>
        <key>StandardOutPath</key>
        <string>/Users/me/log/vm/dockervm.log</string>
      </dict>
    </plist>
    

    Loaded with:

    launchctl load /Users/matt/Library/LaunchAgents/vm.dockervm.plist
    

    The attempted Docker for Mac setup...

    The Docker for Mac application creates a launchd service:

    ○→ launchctl list | grep -i docker
    -   0   com.docker.helper
    78105   0   com.docker.docker.52512
    

    The service has the following details:

    ○→ launchctl list com.docker.docker.52512
    {
        "LimitLoadToSessionType" = "Aqua";
        "Label" = "com.docker.docker.52512";
        "TimeOut" = 30;
        "OnDemand" = true;
        "LastExitStatus" = 0;
        "PID" = 78105;
        "Program" = "/Applications/Docker.app/Contents/MacOS/Docker";
        "ProgramArguments" = (
            "/Applications/Docker.app/Contents/MacOS/Docker";
        );
        "PerJobMachServices" = {
            "com.apple.tsm.portname" = mach-port-object;
            "com.apple.CFPasteboardClient" = mach-port-object;
            "com.apple.coredrag" = mach-port-object;
            "com.apple.axserver" = mach-port-object;
        };
    };
    

    And has the following process tree:

    ○→ pstree -p 78105
    -+= 00001 root /sbin/launchd
     \-+= 78105 matt /Applications/Docker.app/Contents/MacOS/Docker
       \-+= 78118 matt /Applications/Docker.app/Contents/MacOS/com.docker.osx.hyperkit.linux -watchdog fd:0 -max-restarts 5 -restart-seconds 30
         |--- 78119 matt /Applications/Docker.app/Contents/MacOS/com.docker.osx.hyperkit.linux -watchdog fd:0 -max-restarts 5 -restart-seconds 30
         |--= 78120 matt com.docker.db --url fd://3 --git /Users/matt/Library/Containers/com.docker.docker/Data/database
         |--= 78121 matt com.docker.osxfs --address fd:3 --connect /Users/matt/Library/Containers/com.docker.docker/Data/@connect --control fd:4 --volume-control
         |--= 78122 matt com.docker.slirp --db /Users/matt/Library/Containers/com.docker.docker/Data/s40 --ethernet fd:3 --port fd:4 --introspection fd:5 --diagn
         |-+= 78123 matt com.docker.osx.hyperkit.linux
         | \--- 78125 matt /Applications/Docker.app/Contents/MacOS/com.docker.osx.hyperkit.linux
         \-+= 78124 matt com.docker.driver.amd64-linux -db /Users/matt/Library/Containers/com.docker.docker/Data/s40 -osxfs-volume /Users/matt/Library/Containers
           |--- 78126 matt /Applications/Docker.app/Contents/MacOS/com.docker.driver.amd64-linux -db /Users/matt/Library/Containers/com.docker.docker/Data/s40 -o
           \--- 78130 matt /Applications/Docker.app/Contents/MacOS/com.docker.hyperkit -A -m 2048M -c 3 -u -s 0:0,hostbridge -s 31,lpc -s 2:0,virtio-vpnkit,uuid=
    

    The first problem is /Applications/Docker.app/Contents/MacOS/Docker is the GUI app that sits in your tray rather than the VM, so that's going to be hard to run at boot time. The first descendant /Applications/Docker.app/Contents/MacOS/com.docker.osx.hyperkit.linux looks more like something that manages the VM so we'll start there.

    Get the full command of the hyperkit process

    ○→ ps -fp 78105
      UID   PID  PPID   C STIME   TTY           TIME CMD
      501 78105 82644   0  2:08am ??         0:00.03 /Applications/Docker.app/Contents/MacOS/com.docker.osx.hyperkit.linux -watchdog fd:0 -max-restarts 5 -restart-seconds 30
    

    Get the working directory of the process

    ○→ sudo lsof -p 78105 | grep cwd
    com.docke 78105 matt  cwd      DIR                1,4       748 63186601 /Users/matt/Library/Containers/com.docker.docker/Data
    

    Create a launchd plist file /Library/LaunchDaemons/com.you.docker.plist containing the details

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
      <key>Label</key>
        <string>com.you.docker</string>
      <key>ProgramArguments</key>
        <array>
          <string>/Applications/Docker.app/Contents/MacOS/com.docker.osx.hyperkit.linux</string>
          <string>-watchdog</string>
          <string>fd:0</string>
          <string>-max-restarts</string>
          <string>5</string>
          <string>-restart-seconds</string>
          <string>30</string>
        </array>
      <key>UserName</key>
        <string>youruser</string>
      <key>WorkingDirectory</key>
        <string>/Users/youruser/Library/Containers/com.docker.docker/Data</string>
      <key>RunAtLoad</key>
        <true/>
    </dict>
    </plist>
    

    Which you can load with

    sudo launchctl load -w /Library/LaunchDaemons/com.you.docker.plist
    

    Then... nothing. Check the logs

    ○→ tail /var/log/system.log
    Mar  7 02:23:26 mac Docker[87728]: Acquired task manager lock
    Mar  7 02:23:26 mac Docker[87728]: Maximum number of file descriptors is 10240
    Mar  7 02:23:26 mac Docker[87728]: Failed to read watchdog handshake
    Mar  7 02:23:26 mac com.apple.xpc.launchd[1] (com.you.docker[87728]): Service exited with abnormal code: 1
    

    You'll notice the command we are running specifies an option -watchdog fd:0 which will be the stdin of the hyperkit process so there is probably something missing.

    If I run Docker normally again and look at what FD 0 is for the hyperkit process when it's working

    ○→ sudo lsof -p 88360
    COMMAND     PID USER   FD     TYPE             DEVICE  SIZE/OFF     NODE NAME
    com.docke 88360 matt    0     PIPE 0xff80ce577ca2ed91     16384          ->0xff80ce5776aa8d51
    

    Then looking at PIPE 0xff80ce577ca2ed91 across the system you'll see the main Docker process has the pipe open for many processes.

    ○→ sudo lsof | grep 0xff80ce577ca2ed91
    COMMAND     PID             USER   FD      TYPE             DEVICE    SIZE/OFF     NODE NAME
    Docker    88349             matt   13      PIPE 0xff80ce5776aa8d51       16384          ->0xff80ce577ca2ed91
    com.docke 88360             matt    0      PIPE 0xff80ce577ca2ed91       16384          ->0xff80ce5776aa8d51
    com.docke 88362             matt    0      PIPE 0xff80ce577ca2ed91       16384          ->0xff80ce5776aa8d51
    com.docke 88363             matt    0      PIPE 0xff80ce577ca2ed91       16384          ->0xff80ce5776aa8d51
    com.docke 88364             matt    0      PIPE 0xff80ce577ca2ed91       16384          ->0xff80ce5776aa8d51
    com.docke 88365             matt    0      PIPE 0xff80ce577ca2ed91       16384          ->0xff80ce5776aa8d51
    com.docke 88366             matt    0      PIPE 0xff80ce577ca2ed91       16384          ->0xff80ce5776aa8d51
    

    Trying to load the service again without the -watchdog fd:0 options results in the same error.

    So it looks like the main Docker app does some setup that is required to run the VM. I haven't been able to find the source code for whatever deals with the watchdog option so not sure what it expects.

    Maybe raise an issue or feature request on https://github.com/docker/for-mac to get the details of running the VM standalone from the App.