Перенос Docker-контейнеров между серверами сводится к трём вещам: образам, томам с данными и конфигурации окружения. Контейнер сам по себе не хранит состояние — всё важное лежит в volume и в переменных .env, поэтому «перенести контейнер» на практике означает перенести образ и данные, а затем поднять его заново на новом хосте. Если Docker на новом сервере ещё не установлен, начните с базовой установки Docker на VPS, а для многоконтейнерных проектов пригодится статья про Docker Compose.
Метод 1: docker save + docker load
Подходит, когда между серверами нет общего Registry и нужно передать образ одним файлом — например, при разовом переезде на новый VPS.
# На старом сервере — сохраняем образ со всеми слоями
docker save myapp:latest | gzip > myapp.tar.gz
# Передаём на новый сервер
scp myapp.tar.gz root@NEW_SERVER:/tmp/
# На новом сервере — загружаем образ
docker load < /tmp/myapp.tar.gz
# Проверяем, что образ на месте и с тем же ID
docker images | grep myapp
Минус метода — размер файла: образ с толстым базовым слоем (например, полный Ubuntu вместо Alpine) легко выходит за несколько гигабайт, и передача по медленному каналу занимает часы.
Метод 2: через Docker Registry (рекомендуется)
Registry передаёт только недостающие слои, поддерживает откат на предыдущий тег и не требует держать файл образа на диске между серверами.
# Тегируем и пушим в Registry
docker tag myapp:latest registry.example.com/myapp:latest
docker push registry.example.com/myapp:latest
# На новом сервере
docker login registry.example.com
docker pull registry.example.com/myapp:latest
Если используете docker-compose, для переноса достаточно скопировать docker-compose.yml и тома данных — образы пересоберутся локально или скачаются из Registry командой docker compose up -d.
Разбор типичных ошибок при переносе
- Error response from daemon: conflict: unable to delete ... (must be forced) — образ используется другим тегом или запущенным контейнером; сначала остановите контейнеры, использующие образ, либо добавьте флаг
-fкdocker rmi. - open /tmp/myapp.tar.gz: no such file or directory —
scpоборвался на середине передачи или указан не тот путь; проверьте размер файла на обеих сторонах командойls -laи сверьте контрольную суммуsha256sum. - unauthorized: authentication required — на новом сервере не выполнен
docker loginк приватному Registry либо истёк токен доступа. - Error: No such image: myapp:latest — опечатка в теге при
docker tagили образ не был запушен до попыткиpullна другом сервере. - toomanyrequests: You have reached your pull rate limit — анонимные запросы к Docker Hub ограничены по IP; выполните
docker loginс учётной записью или используйте собственный Registry.
Перенос томов (volumes)
Образ переносит только код и зависимости приложения. База данных, загруженные файлы и логи живут в volume и переносятся отдельно.
# Экспорт тома во временный архив
docker run --rm -v myapp_data:/data -v $(pwd):/backup \
alpine tar czf /backup/volume_backup.tar.gz -C /data .
# Импорт на новом сервере в заранее созданный том
docker volume create myapp_data
docker run --rm -v myapp_data:/data -v $(pwd):/backup \
alpine tar xzf /backup/volume_backup.tar.gz -C /data
Сравнение способов переноса
| Критерий | docker save/load | Docker Registry |
|---|---|---|
| Что передаётся | Полный архив образа | Только новые слои |
| Нужен ли отдельный сервис | Нет | Да, Registry |
| Откат на прошлую версию | Нужно хранить старые архивы вручную | Просто смена тега |
| Подходит для | Разового переезда одного сервера | Регулярных деплоев и нескольких серверов |
Когда эти способы не подходят
- Контейнер держит открытое сетевое соединение с состоянием (например, поднятую WebSocket-сессию) — перенос всегда рвёт такие соединения, нужен маршрут с graceful shutdown на уровне приложения.
- Контейнер завязан на устройства хоста через
--device(GPU, USB-токен) — образ переедет, но без такого же устройства на новом сервере часть функциональности не заработает. - Данные меняются во время экспорта тома — если приложение не остановлено,
tarна живом volume базы данных может забрать файл в несогласованном состоянии; для СУБД переносите не файлы тома, а дамп средствами самой базы.
Чеклист миграции Docker
| Шаг | Статус |
|---|---|
| Установить Docker на новом сервере | ⬜ |
| Перенести docker-compose.yml | ⬜ |
| Перенести образы или настроить pull из Registry | ⬜ |
| Перенести тома данных | ⬜ |
| Обновить .env файлы | ⬜ |
| Проверить на новом сервере | ⬜ |
| Переключить DNS | ⬜ |
Проверка после переноса
Перед переключением DNS убедитесь, что стек работает на новом сервере так же, как на старом.
# Все контейнеры подняты и без перезапусков
docker compose ps
# Логи без ошибок за последние 5 минут
docker compose logs --since=5m
# Приложение отвечает по внутреннему порту
curl -I http://127.0.0.1:8080/health
# Данные совпадают: сверяем количество строк в ключевой таблице
docker exec -it myapp_db psql -U app -c "SELECT count(*) FROM orders;"
Если счётчики и логи совпадают со старым сервером, переключайте DNS и только после этого останавливайте контейнеры на старой машине.
Перед остановкой контейнеров на старом сервере убедитесь, что все данные синхронизированы — особенно если используются persistent volumes с базой данных: последняя запись, сделанная после снятия дампа, на новом сервере отсутствует.