├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── LICENSE ├── README.md └── docker-migrate.sh /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | # Describe the bug 11 | A clear and concise description of what the bug is. 12 | 13 | # To Reproduce 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | # Expected behavior 21 | A clear and concise description of what you expected to happen. 22 | 23 | # Docker container info 24 | Please provide the output of `docker inspect [container]` 25 | 26 | # Run logs 27 | Please provide the output logs by running `./docker-migrate.sh [regular commands] > output.txt 2>&1` and putting the context of `output.txt` here 28 | 29 | # Docker versions 30 | Please provide the output of `docker version` on both the local and remote host. 31 | 32 | ## Local host: 33 | 34 | ## Remote host: 35 | 36 | # Docker compose version 37 | Please provide the output of `docker compose version` on both the local and remote host. 38 | 39 | ## Local host: 40 | 41 | ## Remote host: 42 | 43 | # Additional context 44 | Add any other context about the problem here. 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Adam Doussan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # docker-migrate 2 | Based off of docker-volumes.sh by Ricardo Branco https://github.com/ricardobranco777/docker-volumes.sh 3 | 4 | THIS SCRIPT IS PROVIDED AS IS. You assume all responsibility if things go wrong. Have a backup, 5 | assume the worst will happen, and consider it a happy accident if this script works. 6 | 7 | Migrates a docker container from one host to another, including volume data and any options set on 8 | the container. The original container will be brought down as part of this process, but will be 9 | started back up after the required snapshots have been taken. It is recommended that you validate 10 | the new container before destroying the old one. 11 | 12 | This is primarily intended to be used for a isolated container that has been manually created, or 13 | that has data on it that can't be migrated in another way. If you have a complicated setup, or 14 | have a way to recreated the container and its data without migrating it, this script if probably 15 | not for you. 16 | 17 | IF YOUR CONTAINER HAS VOLUMES: Volumes are assumed to be external, you will have to create them on 18 | the new host before running this script. 19 | 20 | # Requirements 21 | Docker must already be installed on both the local and remote host. 22 | [Docker compose V2](https://stackoverflow.com/a/66516826) must be available on both hosts. You can verify that the 23 | V2 api is installed by running `docker compose version`. 24 | 25 | 26 | If your host does not have internet access, `ubuntu:18.04` must be available on the local and remote host, and 27 | `stedolan/jq` must be available on the local host. `red5d/docker-autocompose` must also be available on the local host. 28 | 29 | # Usage 30 | 31 | `./docker-migrate.sh [-v|--verbose] ` 32 | 33 | Ex: `./docker-migrate.sh uptime-kuma root 10.0.0.0` 34 | 35 | This example mill migrate the uptime-kuma container to host 10.0.0.0 using user root. It is 36 | recommended that you set up an SSH keypair, otherwise you will have to enter the password 37 | multiple times. 38 | 39 | # Podman 40 | 41 | NOTE: This is untested, but I have left it in based off of the docker-volumes.sh script 42 | 43 | To use [Podman](https://podman.io) instead of Docker, prepend `DOCKER=podman` to the command line to set the `DOCKER` 44 | environment variable. 45 | 46 | # Notes 47 | * This script could have been written in Python or Go, but the tarfile module and the tar package lack support for writing sparse files. 48 | * We use the Ubuntu 18.04 Docker image with GNU tar v1.29 that uses **SEEK\_DATA**/**SEEK\_HOLE** to [manage sparse files](https://www.gnu.org/software/tar/manual/html_chapter/tar_8.html#SEC137). 49 | * To see the volumes that would be processed run `docker container inspect -f '{{json .Mounts}}' $CONTAINER` and pipe it to either [`jq`](https://stedolan.github.io/jq/) or `python -m json.tool`. 50 | 51 | # Bugs / Features 52 | * Make sure the volumes are defined as such with the `VOLUME` directive. For example, the Apache image lacks them, but you can add them manually with `docker commit --change 'VOLUME /usr/local/apache2/htdocs' $CONTAINER $CONTAINER` 53 | -------------------------------------------------------------------------------- /docker-migrate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # THIS SCRIPT IS PROVIDED AS IS. You assume all responsibility if things go wrong. Have a backup, 4 | # assume the worst will happen, and consider it a happy accident if this script works. 5 | # 6 | # Based off of docker-volumes.sh by Ricardo Branco https://github.com/ricardobranco777/docker-volumes.sh 7 | # 8 | # Migrates a docker container from one host to another, including volume data and any options set on 9 | # the container. The original container will be brought down as part of this process, but will be 10 | # started back up after the required snapshots have been taken. It is recommended that you validate 11 | # the new container before destroying the old one. 12 | # 13 | # This is primarily intended to be used for a isolated container that has been manually created, or 14 | # that has data on it that can't be migrated in another way. If you have a complicated setup, or 15 | # have a way to recreated the container and its data without migrating it, this script if probably 16 | # not for you. 17 | # 18 | # IF YOUR CONTAINER HAS VOLUMES: Volumes are assumed to be external, you will have to create them on 19 | # the new host before running this script. 20 | # 21 | # Example usege: ./docker-migrate.sh uptime-kuma root 10.0.0.0 22 | # This example mill migrate the uptime-kuma container to host 10.0.0.0 using user root. It is 23 | # recommended that you set up an SSH keypair, otherwise you will have to enter the password 24 | # multiple times 25 | # 26 | # NOTES: 27 | # + We use the Ubuntu 18.04 Docker image with tar v1.29 that uses SEEK_DATA/SEEK_HOLE to manage sparse files. 28 | # 29 | 30 | if [[ $1 == "-v" || $1 == "--verbose" ]] ; then 31 | v="-v" 32 | shift 33 | fi 34 | 35 | if [[ $# -ne 3 ]] ; then 36 | echo "Usage: $0 [-v|--verbose] CONTAINER USER HOST" >&2 37 | exit 1 38 | fi 39 | 40 | IMAGE="ubuntu:18.04" 41 | 42 | # Set DOCKER=podman if you want to use podman.io instead of docker 43 | DOCKER=${DOCKER:-"docker"} 44 | 45 | migrate_container() { 46 | echo "Local temp dir: $LOCAL_TMP" 47 | echo "Remote temp dir: $REMOTE_TMP" 48 | 49 | # Stop the container 50 | echo "Stopping container $CONTAINER" 51 | $DOCKER stop $CONTAINER 52 | 53 | # Create a new image 54 | $DOCKER inspect "$CONTAINER" > "$LOCAL_TMP/$CONTAINER.info" 55 | IMAGE_NAME=$($DOCKER run -i stedolan/jq < "$LOCAL_TMP/$CONTAINER.info" -r '.[0].Config.Image') 56 | 57 | echo "Creating image $IMAGE_NAME for container $CONTAINER" 58 | echo "$DOCKER commit $CONTAINER $IMAGE_NAME" 59 | $DOCKER commit $CONTAINER $IMAGE_NAME 60 | 61 | # Save and load image to another host 62 | echo "Saving image and loading it onto remote host, this may take a while, be patient" 63 | $DOCKER save $IMAGE_NAME | ssh $USER@$HOST $DOCKER load 64 | 65 | echo "Saving volumes" 66 | save_volumes 67 | 68 | echo "Saving container options" 69 | save_container_options 70 | 71 | # start container on local host 72 | echo "Restarting local container" 73 | $DOCKER start "$CONTAINER" 74 | 75 | # Copy volumes & compose to new host 76 | echo "Copying volumes and compose to remote host" 77 | scp $TAR_FILE_SRC $USER@$HOST:$TAR_FILE_DST 78 | scp $COMPOSE_FILE_SRC $USER@$HOST:$COMPOSE_FILE_DST 79 | 80 | # Create container with the same options used in the previous container 81 | echo "Creating container on remote host" 82 | ssh $USER@$HOST "$DOCKER compose -f $COMPOSE_FILE_DST create" 83 | 84 | # Load the volumes 85 | echo "Loading volumes on remote host" 86 | load_volumes 87 | 88 | # Start container on remote host 89 | echo "Staring remote container" 90 | ssh $USER@$HOST "$DOCKER start $CONTAINER" 91 | 92 | echo "$0 completed successfully" 93 | } 94 | 95 | save_container_options () { 96 | $DOCKER run --rm -v /var/run/docker.sock:/var/run/docker.sock ghcr.io/red5d/docker-autocompose "$CONTAINER" > "$COMPOSE_FILE_SRC" 97 | } 98 | 99 | get_volumes () { 100 | cat <($DOCKER inspect --type container -f '{{range .Mounts}}{{printf "%v\x00" .Destination}}{{end}}' "$CONTAINER" | head -c -1) | sort -uz 101 | } 102 | 103 | save_volumes () { 104 | if [ -f "$TAR_FILE_SRC" ] ; then 105 | echo "ERROR: $TAR_FILE_SRC already exists" >&2 106 | exit 1 107 | fi 108 | umask 077 109 | # Create a void tar file to avoid mounting its directory as a volume 110 | touch -- "$TAR_FILE_SRC" 111 | tmp_dir=$(mktemp -du -p /) 112 | get_volumes | $DOCKER run --rm -i --volumes-from "$CONTAINER" -e LC_ALL=C.UTF-8 -v "$TAR_FILE_SRC:/${tmp_dir}/${TAR_FILE_SRC##*/}" $IMAGE tar -c -a $v --null -T- -f "/${tmp_dir}/${TAR_FILE_SRC##*/}" 113 | } 114 | 115 | load_volumes () { 116 | tmp_dir=$(mktemp -du -p /) 117 | ssh $USER@$HOST "$DOCKER run --rm --volumes-from $CONTAINER -e LC_ALL=C.UTF-8 -v \"$TAR_FILE_DST:/${tmp_dir}/${TAR_FILE_DST##*/}\":ro $IMAGE tar -xp $v -S -f \"/${tmp_dir}/${TAR_FILE_DST##*/}\" -C / --overwrite" 118 | } 119 | 120 | CONTAINER="$1" 121 | USER="$2" 122 | HOST="$3" 123 | 124 | LOCAL_TMP=$(mktemp -d) 125 | REMOTE_TMP=$(ssh $USER@$HOST "mktemp -d") 126 | 127 | TAR_FILE_NAME="$CONTAINER-volumes.tar.gz" 128 | TAR_FILE_SRC=$(readlink -f "$LOCAL_TMP/$TAR_FILE_NAME") 129 | TAR_FILE_DST="$REMOTE_TMP/$TAR_FILE_NAME" 130 | 131 | COMPOSE_FILE_NAME="$CONTAINER.compose.yml" 132 | COMPOSE_FILE_SRC="$LOCAL_TMP/$COMPOSE_FILE_NAME" 133 | COMPOSE_FILE_DST="$REMOTE_TMP/$COMPOSE_FILE_NAME" 134 | 135 | set -e 136 | migrate_container 137 | --------------------------------------------------------------------------------