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?
目录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 许可协议。转载请注明出处!
相关文章
一次生产环境的docker MySQL故障
问题 昨天下午本来要去吃下午茶,然后前端小伙伴突然说接口怎么崩了,我登上sentry一看,报错了 (2005, Unknown MySQL server host 'mysql' (-3)) 啥意思啊,连不上数据库,host是mysql因为这是在docker环境里 直接ssh连上服务器查看,没啥问题呀,MySQL容器也正常运行着 奇怪 然后我重启了一波docker,结果发现MySQL容器没有正常重
2022-05-27 · docker · 约 1 分钟
在python项目的docker镜像里使用pdm管理依赖
PDM在Docker里替代pip?改Dockerfile调compose,禁用虚拟环境设路径,uwsgi要gcc镜像slim不行。PDM比poetry顺,勉强能用。
2024-08-11 · docker · 约 1 分钟
使用python-slim镜像遇到无法使用PostgreSQL的问题
python-slim镜像用PostgreSQL?报错libpq.so.5缺失,谁在乎?换psycopg2-binary搞定,或者手动安装库,麻烦死了。slim镜像小,PostgreSQL却不行,缺库谁管?用预编译包省事点。
2024-08-20 · docker · 约 1 分钟
从挖矿木马入侵到 Docker Rootless 加固,我的服务器安全复盘
非要等服务器沦为矿场,才肯承认 Docker 默认 Root 权限是给黑客开后门?配置 Rootless 虽琐碎,但也比重装系统划算。连端口映射和 ACL 权限都搞不定,还是趁早关机退网,别在生产环境裸奔了。
2026-01-13 · docker · 约 1 分钟
讨论
共 0 条加载评论中…