返回文章列表文章索引阅读全文
STARBLOG / 阅读手记
docker

The real productivity is here! Docker migration deployment done in just two steps!

Packing application directories and named data volumes, then copying them over to extract and start up—it's an incredibly tedious process. Exporting and importing data volumes is even more redundant; it might as well be a full reinstall. The one-click migration script is pretty cool, but who would actually use such fancy stuff?

曦远 (DealiAxy)约 40 分钟
目录27 个章节

Introduction

Recently, I encountered a scenario requiring the deployment of a rather complex application. Having already deployed this application on another server, I decided to directly migrate the existing setup to save the time and effort of setting it up from scratch.

PS: Surprisingly, this process ended up being more time-consuming than starting from scratch 😂

The good news is that I also created a one-click migration script, which will make similar situations in the future much smoother.

A typical advantage of Docker is its portability.

You only need to package and copy the application-related directories and the docker-compose.yml file from the original server, then extract and deploy them on the target server.

This article documents the process of Docker migration and deployment.

Packaging the Application Directory of the Original Server

You need to locate the docker-compose project directory, which typically contains:

  • docker-compose.yml
  • .env (if applicable)
  • Local directories for other mounted volumes, such as ./data, ./config, ./db, etc.

Then execute:

tar czvf myapp.tar.gz myapp/

Copy

It's recommended to use the scp command for copying.

This is the most convenient method.

scp myapp.tar.gz user@targetIP:/path/

Of course, rsync can also be used, which is more efficient. But personally, I find scp sufficient.

Migrating Data Volumes

If the volumes defined in docker-compose.yml are named volumes, rather than bind mounts (bound to host directories).

For example:

volumes:
  oradata:
  dify_es01_data:

Docker typically manages these data in /var/lib/docker/volumes/.

Data volumes are more complicated and require exporting and importing.

Exporting Volume Data

docker run --rm -v oradata:/data -v $(pwd):/backup alpine tar czf /backup/oradata.tar.gz -C /data .
docker run --rm -v dify_es01_data:/data -v $(pwd):/backup alpine tar czf /backup/dify_es01_data.tar.gz -C /data .

Copy

Continue using scp to export

scp oradata.tar.gz dify_es01_data.tar.gz user@remote:/your/path/

Create Empty Volumes

Create empty volumes on the target server

docker volume create oradata
docker volume create dify_es01_data

Import Data

Import data to volumes

docker run --rm -v oradata:/data -v $(pwd):/backup alpine sh -c "cd /data && tar xzf /backup/oradata.tar.gz"
docker run --rm -v dify_es01_data:/data -v $(pwd):/backup alpine sh -c "cd /data && tar xzf /backup/dify_es01_data.tar.gz"

Unpack & Start

tar xzvf myapp.tar.gz
cd myapp/
docker-compose up -d

One-Click Migration Script (一键迁移脚本)

Going through so many steps is still too cumbersome.

I asked the "big model grandfather" for help and designed a one-click migration script.

After repeated polishing, this script offers a really good experience. With just over a hundred lines of code, it can automatically identify data volumes, pack them into large folders, and copy them to the target server.

Feel free to try it out if you need it.

Save the following file as docker-app-pack.sh.

#!/bin/bash

Docker Compose Application Packaging Script

set -e

Simplified Logging Functions

log() { echo "⏰ [$(date +'%H:%M:%S')] $1" >&2; } error() { echo "❌ [ERROR] $1" >&2; exit 1; }

Check Docker

! docker info >/dev/null 2>&1 && error "Docker is not running"

Discover Project-Related Docker Volumes

find_project_volumes() { local app_dir="$1" local project_name=$(basename "$app_dir")

log "🔍 Search for project-related Docker volumes (prefix: $project_name)"
docker volume ls --format "{{.Name}}" | grep "^${project_name}[_-]" || true

}

Export Data Volume

export_volume() {
    local volume="$1" backup_dir="$2"
    log "💾 Exporting data volume: $volume"
    docker run --rm -v "$volume:/data" -v "$backup_dir:/backup" alpine \
        sh -c "cd /data && tar czf /backup/${volume}.tar.gz ."
}

Package App Directory

package_app() { local app_dir="$1" backup_dir="$2" app_name="$3" log "📦 Packaging app directory (打包应用目录): $app_dir" tar czf "${backup_dir}/${app_name}_app.tar.gz" -C "$(dirname "$app_dir")" "$(basename "$app_dir")" }

Create Final Compressed Package

create_package() { local backup_dir="$1" app_name="$2" output_dir="$3" log "🗜️ Create final compressed package" tar czf "${output_dir}/${app_name}.tar.gz" -C "$backup_dir" . }

Upload to Server

upload_file() {
    local file="$1" server="$2"
    [[ -z "$server" ]] && return 0
    log "🚀 Upload to server: $server"
    scp "$file" "$server"
}

Main Function

main() { echo "🐳 === Docker Compose Application Packaging Tool === 🐳" echo

# Get input
read -p "📁 Application directory path: " app_dir
[[ ! -d "$app_dir" ]] && error "Directory does not exist: $app_dir"

app_name=$(basename "$app_dir")
read -p "📦 Application name [$app_name]: " input_name
[[ -n "$input_name" ]] && app_name="$input_name"

# Auto-discover data volumes
auto_volumes=($(find_project_volumes "$app_dir"))
echo "💾 Discovered data volumes:"
if [[ ${#auto_volumes[@]} -eq 0 || (${#auto_volumes[@]} -eq 1 && -z "${auto_volumes[0]}") ]]; then
    echo "   └── None"
else
    for vol in "${auto_volumes[@]}"; do
        [[ -n "$vol" ]] && echo "   └── $vol"
    done
fi

read -p "➕ Additional data volumes (space-separated): " extra_volumes
volumes=(${auto_volumes[@]} $extra_volumes)

read -p "🌐 Upload server (user@host:/path): " server

# Show summary
echo
echo "📋 === Operation Summary ==="
echo "📂 Application directory: $app_dir"
echo "📦 Output file: ${app_name}.tar.gz"
echo "💾 Data volumes:"
if [[ ${#volumes[@]} -eq 0 || (${#volumes[@]} -eq 1 && -z "${volumes[0]}") ]]; then
    echo "   └── None"
else
    for vol in "${volumes[@]}"; do
        [[ -n "$vol" ]] && echo "   └── $vol"
    done
fi
echo "🌐 Upload server: ${server:-None}"
echo

read -p "✅ Confirm execution? (y/N): " confirm
[[ "$confirm" != [yY] ]] && exit 0

# Execute backup
backup_dir="/tmp/backup_$$"
mkdir -p "$backup_dir"
trap "rm -rf '$backup_dir'" EXIT

# Package application
package_app "$app_dir" "$backup_dir" "$app_name"

# Export data volumes
for vol in "${volumes[@]}"; do
    [[ -n "$vol" ]] && export_volume "$vol" "$backup_dir"
done

# Create final package
output_file="${app_name}.tar.gz"
create_package "$backup_dir" "$app_name" "$(pwd)"

# Upload
upload_file "$output_file" "$server"

echo
echo "🎉 === Backup Complete! ==="
echo "📁 File: $output_file"
echo "📏 Size: $(du -h "$output_file" | cut -f1)"
echo "✨ Backup completed successfully! ✨"

}

Run the main function

main "$@"

### Unpacking Script

There is a corresponding unpacking script, `docker-app-unpack.sh`

```bash
#!/bin/bash

set -e

Simplified Log Functions

log() { echo "⏰ [$(date +'%H:%M:%S')] $1" >&2; } error() { echo "❌ [ERROR] $1" >&2; exit 1; }

Check Docker

check_docker() { log "🔍 Checking Docker service..." ! docker info >/dev/null 2>&1 && error "Docker is not running or cannot be accessed." log "✅ Docker service is running normally." }

Unpack the Main Package

unpack_package() { local package_file="$1" temp_dir="$2" log "📦 解压主包: $package_file 到 $temp_dir" tar xzf "$package_file" -C "$temp_dir" }

Import Application Directory (导入应用目录)

import_app() { local app_tar_file="$1" target_dir="$2" log "📂 Import application directory (导入应用目录): $app_tar_file to $target_dir" mkdir -p "$target_dir" tar xzf "$app_tar_file" -C "$target_dir" --strip-components=1 }

Import Data Volume

    local volume_tar_file="$1" volume_name="$2"
    log "💾 Importing data volume: $volume_name (from $volume_tar_file)"

    if docker volume inspect "$volume_name" >/dev/null 2>&1; then
        read -p "Data volume '$volume_name' already exists. Overwrite? (y/N): " confirm_overwrite
        if [[ "$confirm_overwrite" != [yY] ]]; then
            log "Skipping import of data volume '$volume_name'."
            return 0
        fi
        log "Removing existing data volume '$volume_name'..."
        docker volume rm "$volume_name" >/dev/null
    fi

    log "Creating data volume '$volume_name'..."
    docker volume create "$volume_name" >/dev/null

    log "Importing data into data volume '$volume_name'..."
    docker run --rm -v "$volume_name:/data" -v "$(dirname "$volume_tar_file"):/backup" alpine \
        sh -c "tar xzf /backup/$(basename "$volume_tar_file") -C /data"
    log "✅ Data volume '$volume_name' imported successfully."
}

Main Function

main() { echo "🐳 === Docker Compose Application Unpacking Tool === 🐳" echo

check_docker

read -p "📦 Enter the path of the .tar.gz file to unpack: " package_file
[[ ! -f "$package_file" ]] && error "File not found: $package_file"

# Create temporary directory
local temp_dir
temp_dir="$(mktemp -d -t docker-unpack-XXXXXX)"
log "Created temporary directory: $temp_dir"
trap "log 'Cleaning up temporary directory: $temp_dir'; rm -rf '$temp_dir'" EXIT

unpack_package "$package_file" "$temp_dir"

echo
echo "📋 === Unpack Summary ==="
echo "📦 Source file: $package_file"
echo "📁 Temporary extraction directory: $temp_dir"

# Find application directory package
local app_tar_found=false
for f in "$temp_dir"/*_app.tar.gz; do
    if [[ -f "$f" ]]; then
        app_tar_file="$f"
        app_tar_found=true
        break
    fi
done

if ! $app_tar_found; then
    error "Application directory file (*_app.tar.gz) not found in the extracted package."
fi

local default_app_dir="$(pwd)/$(basename "${app_tar_file%_app.tar.gz}")"
read -p "📂 Enter target path for application directory extraction [$default_app_dir]: " target_app_dir
[[ -z "$target_app_dir" ]] && target_app_dir="$default_app_dir"

echo "Application directory will be extracted to: $target_app_dir"

# Find volume packages
local volume_tar_files=("$temp_dir"/*.tar.gz)
# Filter out application directory package
volume_tar_files=( "${volume_tar_files[@]/$app_tar_file}" )

echo "💾 Found volume packages:"
if [[ ${#volume_tar_files[@]} -eq 0 || (${#volume_tar_files[@]} -eq 1 && -z "${volume_tar_files[0]}") ]]; then
    echo "   └── None"
else
    for vol_file in "${volume_tar_files[@]}"; do
        [[ -n "$vol_file" ]] && echo "   └── $(basename "$vol_file")"
    done
fi
echo

read -p "✅ Confirm execution? (y/N): " confirm
[[ "$confirm" != [yY] ]] && exit 0

# Execute unpacking and import
import_app "$app_tar_file" "$target_app_dir"

for vol_file in "${volume_tar_files[@]}"; do
    if [[ -f "$vol_file" ]]; then
        volume_name="$(basename "${vol_file%.tar.gz}")"
        import_volume "$vol_file" "$volume_name"
    fi
done

echo
echo "🎉 === Unpacking Complete! ==="
echo "✨ Application and data volumes have been successfully imported! ✨"

}

Run Main Function

main "$@"


After running, it should look something like this:

```bash
ubuntu@VM-0-3-ubuntu:~/apps-docker$ ./docker-app-unpack.sh
🐳 === Docker Compose Application Unpack Tool === 🐳

⏰ [17:18:17] 🔍 Checking Docker service...
⏰ [17:18:17] ✅ Docker service is running normally.
📦 Please enter the path of the .tar.gz file to unpack: /home/ubuntu/apps-docker/zammad-docker-compose.tar.gz
⏰ [17:18:23] Creating temporary directory: /tmp/docker-unpack-q47OnW
⏰ [17:18:23] 📦 Extracting main archive: /home/ubuntu/apps-docker/zammad-docker-compose.tar.gz to /tmp/docker-unpack-q47OnW

📋 === Unpack Summary ===
📦 Source file: /home/ubuntu/apps-docker/zammad-docker-compose.tar.gz
📁 Temporary extraction directory: /tmp/docker-unpack-q47OnW
📂 Please enter the target path for application directory extraction [/home/ubuntu/apps-docker/zammad-docker-compose]:
Application directory will be extracted to: /home/ubuntu/apps-docker/zammad-docker-compose
💾 Found data volume archives:
   └── zammad-docker-compose_elasticsearch-data.tar.gz
   └── zammad-docker-compose_postgresql-data.tar.gz
   └── zammad-docker-compose_redis-data.tar.gz
   └── zammad-docker-compose_zammad-backup.tar.gz
   └── zammad-docker-compose_zammad-storage.tar.gz

✅ Confirm execution? (y/N): y
⏰ [17:18:33] 📂 Importing application directory: /tmp/docker-unpack-q47OnW/zammad-docker-compose_app.tar.gz to /home/ubuntu/apps-docker/zammad-docker-compose
⏰ [17:18:33] 💾 Importing data volume: zammad-docker-compose_elasticsearch-data (from /tmp/docker-unpack-q47OnW/zammad-docker-compose_elasticsearch-data.tar.gz)
Data volume 'zammad-docker-compose_elasticsearch-data' already exists. Overwrite? (y/N): y
⏰ [17:18:37] Deleting existing data volume 'zammad-docker-compose_elasticsearch-data'...
⏰ [17:18:37] Creating data volume 'zammad-docker-compose_elasticsearch-data'...
⏰ [17:18:37] Importing data into data volume 'zammad-docker-compose_elasticsearch-data'...
⏰ [17:18:37] ✅ Data volume 'zammad-docker-compose_elasticsearch-data' imported successfully.
⏰ [17:18:37] 💾 Importing data volume: zammad-docker-compose_postgresql-data (from /tmp/docker-unpack-q47OnW/zammad-docker-compose_postgresql-data.tar.gz)
Data volume 'zammad-docker-compose_postgresql-data' already exists. Overwrite? (y/N): y
⏰ [17:18:38] Deleting existing data volume 'zammad-docker-compose_postgresql-data'...
⏰ [17:18:38] Creating data volume 'zammad-docker-compose_postgresql-data'...
⏰ [17:18:38] Importing data into data volume 'zammad-docker-compose_postgresql-data'...
⏰ [17:18:41] ✅ Data volume 'zammad-docker-compose_postgresql-data' imported successfully.
⏰ [17:18:41] 💾 Importing data volume: zammad-docker-compose_redis-data (from /tmp/docker-unpack-q47OnW/zammad-docker-compose_redis-data.tar.gz)
Data volume 'zammad-docker-compose_redis-data' already exists. Overwrite? (y/N): y
⏰ [17:18:41] Deleting existing data volume 'zammad-docker-compose_redis-data'...
⏰ [17:18:41] Creating data volume 'zammad-docker-compose_redis-data'...
⏰ [17:18:41] Importing data into data volume 'zammad-docker-compose_redis-data'...
⏰ [17:18:42] ✅ Data volume 'zammad-docker-compose_redis-data' imported successfully.
⏰ [17:18:42] 💾 Importing data volume: zammad-docker-compose_zammad-backup (from /tmp/docker-unpack-q47OnW/zammad-docker-compose_zammad-backup.tar.gz)
Data volume 'zammad-docker-compose_zammad-backup' already exists. Overwrite? (y/N): y
⏰ [17:18:47] Deleting existing data volume 'zammad-docker-compose_zammad-backup'...
⏰ [17:18:47] Creating data volume 'zammad-docker-compose_zammad-backup'...
⏰ [17:18:47] Importing data into data volume 'zammad-docker-compose_zammad-backup'...
⏰ [17:18:48] ✅ Data volume 'zammad-docker-compose_zammad-backup' imported successfully.
⏰ [17:18:48] 💾 Importing data volume: zammad-docker-compose_zammad-storage (from /tmp/docker-unpack-q47OnW/zammad-docker-compose_zammad-storage.tar.gz)
Data volume 'zammad-docker-compose_zammad-storage' already exists. Overwrite? (y/N): y
⏰ [17:18:49] Deleting existing data volume 'zammad-docker-compose_zammad-storage'...
⏰ [17:18:49] Creating data volume 'zammad-docker-compose_zammad-storage'...
⏰ [17:18:49] Importing data into data volume 'zammad-docker-compose_zammad-storage'...
⏰ [17:18:50] ✅ Data volume 'zammad-docker-compose_zammad-storage' imported successfully.

🎉 === Unpacking Complete! ===
✨ Application and data volumes have been successfully imported! ✨
⏰ [17:18:50] Cleaning up temporary directory: /tmp/docker-unpack-q47OnW

_EOF_

The real productivity is here! Docker migration deployment done in just two steps!

本文作者:
程序设计实验室
版权声明:
本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

相关文章

讨论

共 0 条

良言一句三冬暖,恶语伤人六月寒。

加载评论中…

The real productivity is here! Docker migration deployment done in just two steps! · StarBlog