Why did this serial by path stop working because of too many colons?

I am running SignalK (boating software) in a Pi 4 in my 24 foot fishing boat (crawfish and regular rod fishing). A very important part of that is to be able to follow the bottom when I am at the front steering position. I have a phone set up there that shows the depth from the Garmin plotter and some stuff from the outboard motor (trim, RPMs, temp and so on) from a Sailor Hat NMEA2000 to serial converter. That has worked very well for a couple of years or so. But a few days ago I upgraded Docker, and suddenly the serial ports blocks the server from starting up. These are the offending serials:

    devices:
      - /dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.4:1.0-port0:/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.4:1.0-port0
      - /dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0-port0:/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0-port0

And this is the error message:

invalid spec: /dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.4:1.0-port0:/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.4:1.0-port0: too many colons

I tried to use Serial By ID instead, but that didn’t work:

      - /dev/serial/by-id/usb-067b_2303-if00-port0:/dev/serial/by-id/usb-067b_2303-if00-port0
      - /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0:/dev/serial/by-id/usb-1a86_USB_Serial-if00-port0

The only thing I got working, is what I know from earlier is a problem because on some boots they may change places and not work again:

      - /dev/ttyUSB0:/dev/ttyUSB0 #Sailor Hat
      - /dev/ttyUSB1:/dev/ttyUSB1 #Garmin

This must be some kind of change in the specs. If somebody can help me with a way I can get it working again, or even better, get serial by ID working, that would be great!

Have you tried to wrap each entry in single quotes?

I am surprised it worked before without quoting, as devices mappings actually should only have a single colon, to divide the host and container sides of the mapping.

I assume this works:

devices:
      - '/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.4:1.0-port0:/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.4:1.0-port0'
      - '/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0-port0:/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0-port0'

After sending the response. My response doesn’t make sense to me. I remembered there was a discussion about it a while ago:Device name with ":" - #4 by rimelek

Thank you for answering! Yes, it worked without a hitch for about two years. So something in Docker has changed.

Actually I tried with both double and single quotes, that gives me yaml: line 23: did not find expected '-' indicator. And I see in that post that the issue isn’t really solved. Symlnks is one thing, but as the final posted in the thread says, they can be a problem on connects and disconnects. Which is exactly when the USB0/USB1 creates problems. So should I assume that this is unsolveable for now?

Edit: And another thing, that one is about disks. I don’t know if the same goes for serial ports.

That must have been a syntax error. Proper quoatĂ­tion marks would not change anything unless there is a bug in the compose version.

Why? Looks like I forgot to answer in the original topic, but a symlink is just a pointer to another filepath. And if the device ID changes, being able to define it directly in the compose file would not help. Of course something like /dev/ttyUSB0 is not a good way to refer to a USB disk if you don’t want it to change. The same way as using /dev/sda would not guarantee the disk to always be sda.

You could label the USB disks by using e2label or tune2fs and it would create a symbolic link too under /dev/disk/by-label/.

Did you have an old Docker that you updated? The other topic is about 1 and a half year old

Thanks for answering! First: Not a disk, they are serial ports. So I don’t think the label would work.

Second, I do a sudo apt-get full upgrade every month, I assume that it should upgrade Docker too. Still it suddenly stopped working after my last upgrade.

And third, the path to the serial ports never change, that’s why I was using those. So I am sure you’re right, that there is a syntthax error somewhere, I just coulnd’t find it. This is the code that throws the error, with single quotes separately around the external and internal port ID:

    devices:
      - '/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.4:1.0-port0':'/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.4:1.0-port0'
      - '/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0-port0':'/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0-port0'

If I use the code in the post from @meyay above (with single quotes at the begining and the end of the line) I get the same too many colons error.

Good point. I actually realized it when I was writing my answer but forgot about it by the time I wrote “disk”. So yes, the label would not work, but my original intention with mentioning the label was that even that would create a link and that links is making sure the you have a persistent path to the same hardware.

The syntax error is that you quoted the source and the target separately. This mapping should be a single string. Why the exact error message was shown, I’m not sure. I would have expected mentioning an that a new line is missing first, but quotes are supported only around the string in yaml and this would be the same error message with this yaml:

test:
  - 'a':'b'

and this command:

cat test.yml | yq

if yq is installed.

I still don’t understand what the problem is with the symbolic link. Maybe I wasn’t clear, but I meant to create a symbolic link pointing to your persistent devide path.

You could try, if serial by path works, when you use the unicode character representation for every colon ( except the host:container separator): \u0003A.

To pinpoint whether it’s a compose or general docker issue, you can try to create the container using docker run. You can use Decomposerize to translate the compose specification to a docker run command.

Furthermore, you can raise an issue in the compose github project:

I had it happen again today with the USB0/USB1. I was out with a friend fishing for mackrell, and when I fired up the phone to use it as a depth meter in the bough (reels with meters, so we know we are fishing around 1/2 to 2/3 from the bottom, where they usually go), and after my nightly backup I couldn’t get a reading because the two devices had changed places. Easily fixable, but very annoying!

@rimelek There is of course no problems at all with a symlink if it works! And I followed the instructions on the link, and I believe I got it up and running. I wanted to use serial by ID because that never changes even if I should take out the Pi and by accident plug the USB serials in the wrong ports. I don’t understand the @PWD part, but this seemed to work:

ln -s "/dev/serial/by-id/usb-1a86_USB_Serial-if00-port0" "$PWD/garmin-usb-0"
And:
ln -s "/dev/serial/by-id/usb-067b_2303-if00-port0" "$PWD/sailorhat-usb-0"

The symlinks were created, and the Garmin and Sailor Hat work in SignalK, I could turn on the mains power (with Home Assistant running in another Docker Container) and after a minute I could see the depth and water temp, so I think it’s a total success. The only thing I wonder is: What happens if the Pi boots without one or both attached? Will the symlink disappear, or will it just be dead, so it works when the port is connected again?

@meyay The unicode didn’t change anything, I’m afraid. And it seems to be a general Docker issue, the start line without Docker Compose threw this:

docker: bad format for path: /dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.4:1.0-port0:/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.4:1.0-port0.

It will not disappear, its target will be just unavailable, until it is available again.

Then the right place to raise this issue is the Moby Github project:

Thanks, then the symlinks should work for me. I will try to put in an issue and see if anything happens.

1 Like

Got an answer now, it’s part of a merge in Docker Compose from March that issupposed to stop this working, as far as I undestand. I have no idea why my system suddenly reacted now. Maybe I have had some incpomplete updates, or something like that. But so far the symlinks work perfectly so I’ll be back in this thread if I ever have any problems, which I don’t expect to have. Thanks for all the help!

Edit: I had misunderstood, thaJeztah at GitHub asked me to raise the issue on Docker Compose Git, so I did that now.

If thaJeztah writes it, it must be correct. Though I am surprised, as it also happened in your test with the docker cli. So either both clients (docker cli + compose plugin) and/or the engine is affect.

Either that, or Docker Compose managed to “mask” it for Docker in a way that made it work.

Could be. If you check the API endpoint /v1.45/containers/create, it uses this structure for the device information:

So indeed parsing the host and container sides must be done in the client. But wouldn’t that mean that the docker cli is affected as well?

I don’t have the slightest idea. All this is way over my head. :flushed:

If you share the output of docker inspect for the current container, I could try to create a curl command that directly drives the api to create the container. It may also be a good detail to add to the issue, to prove whether it generally works.

Of course! This is the working container:

docker inspect SignalK
[
    {
        "Id": "51a006623160c86c21a97c18a09b3fb3fa729075cc91f425437d56b39df4de0a",
        "Created": "2024-08-09T07:35:39.771097922Z",
        "Path": "sh",
        "Args": [
            "/home/node/signalk/startup.sh"
        ],
        "State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 252065,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2024-08-09T07:35:39.881748021Z",
            "FinishedAt": "0001-01-01T00:00:00Z"
        },
        "Image": "sha256:8f99fad9e74f1dd723d6d92023284a08e4b058a9a5baeee15a95bbec125caacc",
        "ResolvConfPath": "/media/pi/Docker/Docker-System/containers/51a006623160c86c21a97c18a09b3fb3fa729075cc91f425437d56b39df4de0a/resolv.conf",
        "HostnamePath": "/media/pi/Docker/Docker-System/containers/51a006623160c86c21a97c18a09b3fb3fa729075cc91f425437d56b39df4de0a/hostname",
        "HostsPath": "/media/pi/Docker/Docker-System/containers/51a006623160c86c21a97c18a09b3fb3fa729075cc91f425437d56b39df4de0a/hosts",
        "LogPath": "/media/pi/Docker/Docker-System/containers/51a006623160c86c21a97c18a09b3fb3fa729075cc91f425437d56b39df4de0a/51a006623160c86c21a97c18a09b3fb3fa729075cc91f425437d56b39df4de0a-json.log",
        "Name": "/SignalK",
        "RestartCount": 0,
        "Driver": "overlay2",
        "Platform": "linux",
        "MountLabel": "",
        "ProcessLabel": "",
        "AppArmorProfile": "",
        "ExecIDs": null,
        "HostConfig": {
            "Binds": [
                "/dev:/dev:rw",
                "/media/pi/Docker/Docker-Compose/SignalK/SignalK-data:/home/node/.signalk:rw"
            ],
            "ContainerIDFile": "",
            "LogConfig": {
                "Type": "json-file",
                "Config": {
                    "max-size": "10m"
                }
            },
            "NetworkMode": "host",
            "PortBindings": {},
            "RestartPolicy": {
                "Name": "no",
                "MaximumRetryCount": 0
            },
            "AutoRemove": false,
            "VolumeDriver": "",
            "VolumesFrom": null,
            "ConsoleSize": [
                0,
                0
            ],
            "CapAdd": null,
            "CapDrop": null,
            "CgroupnsMode": "private",
            "Dns": null,
            "DnsOptions": null,
            "DnsSearch": null,
            "ExtraHosts": [],
            "GroupAdd": [
                "20"
            ],
            "IpcMode": "private",
            "Cgroup": "",
            "Links": null,
            "OomScoreAdj": 0,
            "PidMode": "",
            "Privileged": true,
            "PublishAllPorts": false,
            "ReadonlyRootfs": false,
            "SecurityOpt": [
                "label=disable"
            ],
            "UTSMode": "",
            "UsernsMode": "",
            "ShmSize": 67108864,
            "Runtime": "runc",
            "Isolation": "",
            "CpuShares": 0,
            "Memory": 0,
            "NanoCpus": 0,
            "CgroupParent": "",
            "BlkioWeight": 0,
            "BlkioWeightDevice": null,
            "BlkioDeviceReadBps": null,
            "BlkioDeviceWriteBps": null,
            "BlkioDeviceReadIOps": null,
            "BlkioDeviceWriteIOps": null,
            "CpuPeriod": 0,
            "CpuQuota": 0,
            "CpuRealtimePeriod": 0,
            "CpuRealtimeRuntime": 0,
            "CpusetCpus": "",
            "CpusetMems": "",
            "Devices": [
                {
                    "PathOnHost": "/media/pi/Docker/Docker-Compose/SignalK/sailorhat-usb-0",
                    "PathInContainer": "/dev/ttyUSB0",
                    "CgroupPermissions": "rwm"
                },
                {
                    "PathOnHost": "/media/pi/Docker/Docker-Compose/SignalK/garmin-usb-0",
                    "PathInContainer": "/dev/ttyUSB1",
                    "CgroupPermissions": "rwm"
                }
            ],
            "DeviceCgroupRules": null,
            "DeviceRequests": null,
            "MemoryReservation": 0,
            "MemorySwap": 0,
            "MemorySwappiness": null,
            "OomKillDisable": null,
            "PidsLimit": null,
            "Ulimits": null,
            "CpuCount": 0,
            "CpuPercent": 0,
            "IOMaximumIOps": 0,
            "IOMaximumBandwidth": 0,
            "MaskedPaths": null,
            "ReadonlyPaths": null
        },
        "GraphDriver": {
            "Data": {
                "LowerDir": "/media/pi/Docker/Docker-System/overlay2/812a48fd1032fcce6f5821289f3771a8ff425377a8c1f071e4aa19e5ec134cd5-init/diff:/media/pi/Docker/Docker-System/overlay2/dd0a55fa93c697b16a5d10b066aedefc61a7f9a0bbe693190bee505f0698174c/diff:/media/pi/Docker/Docker-System/overlay2/090a42eed8414a1d5c11cd1142222ab26584634aa0d4b735725aa1a9b53d3ad2/diff:/media/pi/Docker/Docker-System/overlay2/6a5832c7aca4b7a1a148b4ed6330f98bcd9bfac23531016130a9d554186b7e54/diff:/media/pi/Docker/Docker-System/overlay2/d2e2967b632f9f28ed89040eba1ed223190b94f4a0e42c978a355d4d44707cfd/diff:/media/pi/Docker/Docker-System/overlay2/d09a02a08a023f771bfd7f576e9a79adc4c68502d448ba92811e16a30b9f8885/diff:/media/pi/Docker/Docker-System/overlay2/c9e67a3be5656ec6bd0c9972aa44e2e2f400560caf36ef1fdb5797eb5a62886d/diff:/media/pi/Docker/Docker-System/overlay2/a31f7bad96cc0c0e738c37b7bb72800939a93d73c03e03cfe89e6f7418329bd3/diff:/media/pi/Docker/Docker-System/overlay2/59d85abea48b115ac4c483f3cf9fd1274d231fbef1f31cca931f8b3916242d5b/diff:/media/pi/Docker/Docker-System/overlay2/19edc0d2c09320b621a3e72fb680e8bda09cd25bcc080f27304aae8d60238aa9/diff:/media/pi/Docker/Docker-System/overlay2/40fa2da52577f8526c2b2f7952a6d0c966878092f997a89a2c7d60da10dd950b/diff:/media/pi/Docker/Docker-System/overlay2/9b9c11379ba948cdcd5e76f0bc74ad6349502ffdb5d4dd12caffed3b2fd0ee9c/diff:/media/pi/Docker/Docker-System/overlay2/0080a30f4a85c38e0bba0055d63c9faacf55e7bf0f0691c21f5cf835e5f76f79/diff:/media/pi/Docker/Docker-System/overlay2/bf4b2bf81f974dcd996c0f46749bc8f06428fb9e21d09fbc83b5425ea0ba7047/diff",
                "MergedDir": "/media/pi/Docker/Docker-System/overlay2/812a48fd1032fcce6f5821289f3771a8ff425377a8c1f071e4aa19e5ec134cd5/merged",
                "UpperDir": "/media/pi/Docker/Docker-System/overlay2/812a48fd1032fcce6f5821289f3771a8ff425377a8c1f071e4aa19e5ec134cd5/diff",
                "WorkDir": "/media/pi/Docker/Docker-System/overlay2/812a48fd1032fcce6f5821289f3771a8ff425377a8c1f071e4aa19e5ec134cd5/work"
            },
            "Name": "overlay2"
        },
        "Mounts": [
            {
                "Type": "bind",
                "Source": "/dev",
                "Destination": "/dev",
                "Mode": "rw",
                "RW": true,
                "Propagation": "rprivate"
            },
            {
                "Type": "bind",
                "Source": "/media/pi/Docker/Docker-Compose/SignalK/SignalK-data",
                "Destination": "/home/node/.signalk",
                "Mode": "rw",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],
        "Config": {
            "Hostname": "MadMax",
            "Domainname": "",
            "User": "node",
            "AttachStdin": false,
            "AttachStdout": true,
            "AttachStderr": true,
            "ExposedPorts": {
                "3000/tcp": {}
            },
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PORT=3000",
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "IS_IN_DOCKER=true"
            ],
            "Cmd": null,
            "Image": "cr.signalk.io/signalk/signalk-server:latest",
            "Volumes": null,
            "WorkingDir": "/home/node/.signalk",
            "Entrypoint": [
                "sh",
                "/home/node/signalk/startup.sh"
            ],
            "OnBuild": null,
            "Labels": {
                "com.docker.compose.config-hash": "d64872f46d218eaec24f543b2c48b22e972d3776bebb18c45133b574ffb3056e",
                "com.docker.compose.container-number": "1",
                "com.docker.compose.depends_on": "",
                "com.docker.compose.image": "sha256:8f99fad9e74f1dd723d6d92023284a08e4b058a9a5baeee15a95bbec125caacc",
                "com.docker.compose.oneoff": "False",
                "com.docker.compose.project": "signalk",
                "com.docker.compose.project.config_files": "/media/pi/Docker/Docker-Compose/SignalK/docker-compose.yml",
                "com.docker.compose.project.working_dir": "/media/pi/Docker/Docker-Compose/SignalK",
                "com.docker.compose.service": "signalk-server",
                "com.docker.compose.version": "2.29.1",
                "org.opencontainers.image.ref.name": "ubuntu",
                "org.opencontainers.image.version": "22.04"
            }
        },
        "NetworkSettings": {
            "Bridge": "",
            "SandboxID": "d07c28f255014973263c1f6ebe7e874971a8d1441bf820060172b307f189dc70",
            "SandboxKey": "/var/run/docker/netns/default",
            "Ports": {},
            "HairpinMode": false,
            "LinkLocalIPv6Address": "",
            "LinkLocalIPv6PrefixLen": 0,
            "SecondaryIPAddresses": null,
            "SecondaryIPv6Addresses": null,
            "EndpointID": "",
            "Gateway": "",
            "GlobalIPv6Address": "",
            "GlobalIPv6PrefixLen": 0,
            "IPAddress": "",
            "IPPrefixLen": 0,
            "IPv6Gateway": "",
            "MacAddress": "",
            "Networks": {
                "host": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "MacAddress": "",
                    "DriverOpts": null,
                    "NetworkID": "245453f741be61add912647f656e8965e4562889c1bb37ebc7b00ba46822650e",
                    "EndpointID": "bf43fa6aacf461971c245997ecf49f2923e6e3003900a6bbed6e214bdd7a60d0",
                    "Gateway": "",
                    "IPAddress": "",
                    "IPPrefixLen": 0,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "DNSNames": null
                }
            }
        }
    }
]


I don’t think there is any dangerous information there, any passwords wouldn’t be what I use outside of Docker, and if somebody are in my boat trying to hack it they have a much bigger problem than a password, namely a 100 kilos, bearded viking! :rofl:

Edit: But this is of course with the symlinks to the serial ports.

Here is a script that you can either paste into the terminal, or save into a bash script.

I tried to remove as much from the current content configuration as possible, as most settings are inherited by the image anyway.

It uses the container name “device-test” and requires to already exist in the image cache of the docker host (it does not do an implicit docker pull). Make sure to stop your existing container to prevent port conflicts.

The script will only create the container, but not start it, so you will need to execute a docker start device-test after it’s created.

CONTAINER_NAME=device-test
CONFIG=$(cat <<EOF
{
  "Image": "cr.signalk.io/signalk/signalk-server:latest",
  "AttachStdout": true,
  "AttachStderr": true,
  "HostConfig": {
    "Binds": [
      "/dev:/dev:rw",
      "/media/pi/Docker/Docker-Compose/SignalK/SignalK-data:/home/node/.signalk:rw"
    ],
    "LogConfig": {
      "Type": "json-file",
      "Config": {
          "max-size": "10m"
      }
    },
    "NetworkMode": "host",
    "RestartPolicy": {
      "Name": "no",
      "MaximumRetryCount": 0
    },
    "CgroupnsMode": "private",
    "IpcMode": "private",
    "Privileged": true,
    "GroupAdd": [
      "20"
    ],
    "SecurityOpt": [
      "label=disable"
    ],
    "Devices": [
      {
          "PathOnHost": "/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.4:1.0-port0",
          "PathInContainer": "/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.4:1.0-port0",
          "CgroupPermissions": "rwm"
      },
      {
          "PathOnHost": "/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0-port0",
          "PathInContainer": "/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0-port0",
          "CgroupPermissions": "rwm"
      }
    ]
  }
}
EOF
)
curl --silent \
  --unix-socket /var/run/docker.sock \
  "http://v1.45/containers/create?name=${CONTAINER_NAME}" \
  -X POST \
  -H "Content-Type: application/json" \
  -d "${CONFIG}"

Done. It starts, but should I look for some special output or something? I don’t seem to be able to connect to the GUI, on 80 or 3000.

Seems the payload I created is missing something. Since it uses the host network, I would have expected it to bind the ports on the host directly.

Maybe I should have asked you for the compose file instead: I would help to differentiate what parts of the final container configuration are user defined, and what parts are inherited by the image and docker default behavior.

Generally, it seems like good news that the container can be started, as it would indicate that the engine is able to create and start the container without suffering from the “too many collon” problem .