Speed up docker in development without ruby or unison on the host machine.
1.6K
A docker volume container using Unison for fast two-way folder sync. It is much faster than basic docker bind mounts on either Mac OS X or Windows. The Docker folks are working on speeding up the Mac OS X bind mounts but they are still much slower than native speed.
When the development environment is configured correctly with docker-volume-sync, a developer doesn't need to remember any special commands. They just run docker-compose up as they normally would.
docker-volume-sync opens port 5001 after doing an initial sync. Any containers using the synced volume should wait for this port to be open before using the volume. Otherwise they will probably try to access files in the volume that are not there yet. There are several options to use for waiting: wait-for-it, wait-for, and wait4ports.
docker-volume-sync is designed to work with compose. Let's say you have a compose file like the one below. It is mounting the host directory . to /app in the container. The container simply prints the contents of a file and then exits.
version: '3'
services:
app:
build:
context: .
command: ["cat", "/app/test_files/zzz.txt"]
volumes:
- .:/app
To use docker-volume-sync you would change it to look like this:
version: '3'
services:
sync:
image: concordconsortium/docker-volume-sync
volumes:
- sync-volume:/data
- .:/host_data
app:
build:
context: .
entrypoint: ["/bin/bash", "/usr/local/bin/wait-for-it.sh", "sync:5001", "-s", "-t", "30", "--"]
command: ["cat", "/app/test_files/zzz.txt"]
depends_on: [ sync ]
volumes:
- sync-volume:/app
volumes:
sync-volume:
You also need to change the app image to install the wait-for-it script. See the Waiting for Port section below.
These changes to the compose file can also be done using an overlay, so your original compose file can remain untouched. This overlay would look like this:
version: '3'
services:
sync:
image: concordconsortium/docker-volume-sync
volumes:
- sync-volume:/data
- .:/host_data
app:
entrypoint: ["/bin/bash", "/usr/local/bin/wait-for-it.sh", "sync:5001", "-s", "-t", "30", "--"]
depends_on: [ sync ]
volumes:
- sync-volume:/app
volumes:
sync-volume:
In the example above wait-for-it is used to wait for the port. The Dockerfile for that app service is this:
FROM alpine:edge
RUN apk --no-cache add bash ca-certificates
WORKDIR /usr/local/bin
RUN wget https://raw.githubusercontent.com/vishnubob/wait-for-it/ed77b63706ea721766a62ff22d3a251d8b4a6a30/wait-for-it.sh && \
chmod +x wait-for-it.sh
CMD ["cat", "/app/test_files/zzz.txt"]
wait-for-it requires bash, so bash is added, then the wait-for-it script is downloaded, and made executable.
The docker compose command can be used to pass additional options to Unison. A good usage for this
is to ignore files. For example if you want to ignore all log files and dist files you can use this:
...
services:
sync:
image: concordconsortium/docker-volume-sync
command: ["-ignore", "Name *.log", "-ignore", "Path dist"]
volumes:
- sync-volume:/data
- .:/host_data
...
More info about the syntax for ignoring files is here: https://www.cis.upenn.edu/~bcpierce/unison/download/releases/stable/unison-manual.html#ignore
The options passed to Unison are appended to the default Unison options defined in entrypoint.sh. Any ignore options added using docker-compose command will augment not replace the defaults.
If you want to ignore a folder that is being updated on on your host system, you might be able to optimize performance further by adding an additional volume inside of host_data. This technique is described here: https://stackoverflow.com/a/37898591 We don't currently have a need for this kind of optimization, so it hasn't been tested.
docker-volume-sync has a few environment variables that you can alter.
UNISON_DIR - This is the directory which receives data from unison inside docker-volume-sync. This is the directory you should use in other containers. The default is /data
UNISON_HOST_DIR - This should be the mounted host volume. docker-volume-sync will sync this directory with UNISON_DIR. The default is /host_data
UNISON_GID - Group ID for the user running unison inside container.
UNISON_UID - User ID for the user running unison inside container.
UNISON_USER - User name for the sync user ( UID matters more )
UNISON_GROUP - Group name for the sync user ( GID matters more )
If you can't modify the containers using the synced volume to wait for the port, then you shouldn't add the docker-volume-sync service to your app's docker compose file. Instead you should run it separately. Then you should manually wait for the first sync to complete before starting any containers using the synced volume.
docker-volume-sync tries to emulate a simple docker bind mounted volume. As such it treats the UNISON_HOST_DIR as the main source of the files. If there is a conflict it will use the file in the UNISON_HOST_DIR. If the container is not running and a file is removed from UNISON_HOST_DIR but not UNISON_DIR, then docker-volume-sync will delete the file in UNISON_DIR when it starts up.
If docker-volume-sync has been removed and you make changes in UNISON_DIR, when docker-volume-sync starts up you will lose those changes. This is roughly what happens with a bind mount. If you unmount the bind mount, then add files to the unmounted directory, those changes will appear lost when the volume is mounted again. The difference is what the directory looks like when it is disconnected. With a bind mount the directory is empty, with docker-volume-sync the directory will continue to have the files in it. (assuming you just remove docker-volume-sync and don't remove the sync-volume). This has not been a problem in practice, if it causes problems for you, please file an issue, there might be a way to improve the behavior.
Occasionally you might get an error like this:
Fatal error: Warning: the archives are locked. If no other instance of unison is running, the locks should be removed. The file /unison/lk6a655cdeaa391792d60e9febcea4f06a on host 82ad9bbcb013 should be deleted
This error will prevent the sync container from starting. If the sync container can't start then it is difficult to get into it and delete the lock file. There are a couple of options:
remove the sync container and its anonymous volumes: docker-compose rm -v sync
This command effectively removes the whole /unison folder so the problem should go away.
When you run docker-compose up again the sync container will be recreated.
This command does not remove the sync-volume, so it shouldn't take too long to resync.
mount the volumes used by the sync container in a new container and delete the lock file:
docker-compose ps and find the sync container name docker run --volumes-from [sync_container_name] -it --rm bash
this uses a generic bash image./unisonIt should be possible to detect this error in the sync container and either automatically
handle it or at least leave the container running so the user can use docker-compose exec.
Pull requests are welcome. 😊
There are no automated tests. But the files in demo can be used to run a manual test.
First cd into demo
Then run generate_test_files.sh this will generate 200 random files in a test_files directory.
Then run docker-compose up. Docker compose will build a image for a sync service and a image for a app service. After these images are built you should see the sync service syncing all of the random files. And you should see the app image waiting for the files to be sync'd before printing the contents of the zzz.txt file.
This demo folder also includes an example of how to ignore a log file.
This is based off of https://github.com/onnimonni/docker-unison
The idea of bridging two volumes with unison running internally came from docker-sync
This docker image is licensed under GPLv3 because Unison is licensed under GPLv3 and is included in the image. See LICENSE.
Content type
Image
Digest
Size
7.6 MB
Last updated
about 6 years ago
docker pull concordconsortium/docker-volume-sync