Videos
Hey there! I wrote a blog post about speeding up docker context (remote docker development), but this sub doesn't allow me to post links directly. So here you can either go to the blog post, or read it underneath.
https://krystex.github.io/posts/faster-docker-contexts/
So if you are like me, who likes to develop with an remote docker host, you likely used docker contexts for connecting to a remote endpoint:
docker context create remote --docker "host=ssh://user@myhost.org" docker context use remote
With the docker context use command, your remote endpoint is now the default docker endpoint on your system. All commands you execute on your machine are actually executed on your specified ssh host. You can take a look at your contexts with the command docker context ls.
However, depending on your internet connection, you might notice that a command is always a little slow. This happens because on every docker command, a new SSH session to your server is started.
Becoming tedious of this, I wrote a small script to make it faster:
#! /bin/bash
MYHOST=yourwebsite.org
SSHREMOTE=user@$MYHOST
SOCKET=/tmp/docker.remote.sock
# Cleanup proxy'ed socket
function onexit() {
echo "Deleting old socket ..."
rm $SOCKET
}
# Cleanup when Ctrl-C is pressed
trap onexit EXIT
# Delete old socket file if it exists
if [ -f "$SOCKET" ]; then
onexit
fi
echo "Proxying docker from '$YOURWEBSITE' on '$SOCKET' ..."
# Forward remote docker socket to your host
ssh -nNT -L $SOCKET:/var/run/docker.sock $SSHREMOTE
With this command, you take your remote docker socket (/var/run/docker.sock), and mount it at your local system with a temporary file (/tmp/docker.remote.sock). You can configure your remote host and the local socket location. The following lines are just for cleaning up an old socket. The last line is for mounting the remote socket at your host. Let's break it down:
-
-n: so ssh can be run in background -
-N: don't execute a remote command, just forward the socket -
-T: don't start a terminal session, just forward the socket -
-L <localsocket>:<remotesocket>: forward a remote socket to your host
The advantage of this command: it holds a long-running connection to your server of choice. I execute this script in a tmux session so it's always running in the background.
Now you just have to create a context for your forwarded socket:
docker context rm remote docker context create remote --docker "unix:///tmp/docker.remote.sock" docker context use remote
That's it!
Now you can use test your connection:
docker ps
You should see a noticable improvement if don't have the best server or internet connection.
Docker contexts are kind of strange and, in my opinion, seem a bit half-baked. I'm aware that his is an old question, but after spending my afternoon tearing out my hair trying to get this to work for myself, I just want to put this info out here for everyone.
Interpolation is always local
All that docker compose (and docker stack, etc.) is is just a fancy frontend to the underlying backend API (the docker.sock protocol). This allows docker-compose to have a bunch of fancy features that docker engine itself doesn't have. One of those is the ability to use relative paths or expand variables like ${HOME} as you do in your example.
When you use docker-compose in a context, all that happens is that after docker-compose has done all its fancy magic locally on your machine, it then sends the low-level API calls over ssh to the remote engine. This means that whenver you use a fancy magic feature like putting $HOME or ./ in a path, docker compose expands those locally using your system's HOME, i.e. your own local home directory and then sends those requests to the remote engine, where the deployment subsequently fails because those paths don't exist there.
However, using absolute paths works just fine. If you use /home/user/run/nginx.conf instead of ${HOME}/run/nginx.conf, then, in my experience, everything should be fine.
Use named volumes instead
In your question, the file you were trying to mount was a config file, which means that you probably needed full manual control over it, and that's a perfectly valid reason to use a bind mount.
However, if, for example, you're running a database, you won't need to manually edit the database's data directory. As long as the database itself can interact with it, it's fine. In this case, you should use a regular volume instead. To do this, add a volumes: directive to the end of the file and list your needed volume names:
services:
postgres:
restart: always
environment:
POSTGRES_PASSWORD: example
volumes:
- psql-data:/var/lib/postgresql/data
volumes:
psql-data:
If you're using docker swarm, please notice that volumes are not replicated by default, but there are third-party drivers out there and AFAIK the NFS driver option may also be of use, but I can't seem to find much documentation about it.
Use connection persistence
If you, as I did, have a strange issue where running any docker command via ssh takes an obscene amount of time, the solution seems to be setting up ssh to persist your connections. To do this, create a file ~/.ssh/config and add the following lines:
Host example.com
ControlMaster auto
ControlPath ~/.ssh/sockets/%C
ControlPersist 600
Replace example.com with the hostname or IP address of your remote host.
If you get any errors mentioning something like "can't bind to path" or "file not fount", you may need to create the ~/.ssh/sockets directory:
mkdir ~/.ssh/sockets
This is by far no conclusive list of the idiosyncrasies of remote docker contexts, but hopefully this can save some people some time.
You need to explicitly set DOCKER_HOST to access your remote docker host from docker-compose.
From the compose documentation
Compose CLI environment variables
DOCKER_HOST
Sets the URL of the docker daemon. As with the Docker client, defaults to unix:///var/run/docker.sock.
In your given case, docker context use remote sets current context to remote
only for your docker command. docker-compose still uses your default (local) context. In order for docker-compose to detect it, you must pass it via the DOCKER_HOST environment variable.
Example:
$ export DOCKER_HOST=ssh://[email protected]
$ docker-compose up