Move an existing app
Moving an application is a data migration with a deploy attached. Build the new home first, verify it on a temporary hostname, then move DNS. Keep the old host running until the new one has served real traffic.
Shipper does not adopt containers, volumes, or Compose projects created by another tool. It creates new owned resources and leaves the old host untouched.
Inventory before you touch anything
Section titled “Inventory before you touch anything”| Item | What to record |
|---|---|
| Source | Git URL and branch, image and digest, local folder, or Compose file |
| Build | Dockerfile path, build arguments, install and build commands |
| Runtime | Start command, port, health path, user, resource limits |
| Configuration | Environment variables, secret values, config files, feature flags |
| Data | Database engine and version, named volumes, bind mounts, object storage |
| Network | Domains, aliases, redirects, webhooks, allowed origins, OAuth callbacks |
| Scheduled work | Cron jobs, workers, queues, one-shot tasks |
| Access | Users, API keys, tokens, SSH keys, TLS certificates |
Take a restorable backup of the old application before the first change. If the old platform has a tested restore path, use it. If it does not, create one now.
Choose the Shipper source
Section titled “Choose the Shipper source”- Git repository keeps builds repeatable and lets Shipper fetch a branch or commit.
- Local files is for a project that only exists on a workstation. The snapshot is immutable and does not need Git.
- Container image is for an image that is already built and published. Use a digest for a pinned release.
- Compose is for a multi-service stack with its own
compose.yaml. - Inline Dockerfile is for a small definition that does not belong in a repository.
A Compose stack remains one Shipper application. Its services keep their own containers and route targets.

Bring the configuration
Section titled “Bring the configuration”Create the application, then open .env. Add the same variables the old runtime uses, with the correct scope:
- Build variables are available while the image is built.
- Runtime variables are injected into the container.
- Both is the default for values needed in each phase.
Mark passwords, tokens, connection strings, and private keys as secrets. Shipper encrypts saved secret values and returns them masked. Review the change list before applying.
If the old host used .env files baked into the image, remove them from the build context and pass the values through Shipper instead. A public image layer is not a secret store.
For build-only credentials, use a BuildKit secret mount in the Dockerfile instead of an ARG. See Environment variables and secrets.
Bring the data
Section titled “Bring the data”Databases
Section titled “Databases”Use the native dump and restore tools for the engine and version you run. Keep the source and target versions compatible.
# PostgreSQL sourcepg_dump --format=custom --no-owner --dbname=app > app.dump
# PostgreSQL target, inside the new database servicepg_restore --no-owner --dbname=app /tmp/app.dump# MySQL or MariaDB sourcemysqldump --single-transaction --routines --triggers app > app.sql
# MySQL or MariaDB targetmysql app < app.sqlFor a final cutover, freeze writes before the last dump. A dump taken while the application is writing is a point-in-time approximation, not a guaranteed consistent copy.
Shipper application backup plans can also produce native database dumps and restore them as a new copy. They run inside the selected Shipper-owned service and require the current credentials. Use them after the app is on Shipper, not as a substitute for the first migration.
Volumes
Section titled “Volumes”Copy file data with ownership and modes preserved.
# Old hostdocker run --rm -v old_uploads:/from -v "$PWD":/backup alpine \ tar -C /from -czf /backup/uploads.tar.gz .
# New host, after the target volume existsdocker run --rm -v new_uploads:/to -v "$PWD":/backup alpine \ tar -C /to -xzf /backup/uploads.tar.gzReplace new_uploads with the actual Docker volume name shown in the application’s Storage settings. Do not copy a live database directory as a raw volume. Use a native dump or stop every writer first. Shipper’s cold-volume backups reject running or paused containers for the same reason.
Object storage and external services
Section titled “Object storage and external services”Keep external object storage external when possible. If you move it, update the endpoint, bucket, region, access key, and signing configuration in the same change review.
Build the new app beside the old one
Section titled “Build the new app beside the old one”- Create the application in Shipper.
- Deploy it on a temporary hostname such as
new-shop.example.com. - Point that temporary hostname at the new server before saving the domain, so the certificate can be issued.
- Restore the data.
- Run the same smoke tests you would run after a normal deploy.
dig +short new-shop.example.comcurl -sS -o /dev/null -w '%{http_code}\n' https://new-shop.example.comcurl -sS https://new-shop.example.com/healthIf the app has background workers, start them only after the database restore. A worker that writes to the old database while the new app reads a restored copy creates two sources of truth.
Cut over DNS
Section titled “Cut over DNS”Lower the TTL on the production hostname at least one TTL period before the move. Keep the old host running.
- Put the old app into a write freeze or maintenance mode.
- Take the final database dump and restore it to the new app.
- Change the production DNS record to the new server.
- Add the production hostname in Domains and save.
- Verify DNS, TLS, and the serving release in Domains → Verify.
- Run real read and write tests through the production hostname.
- Remove the temporary hostname.
- Keep the old host stopped but intact for the rollback window.
dig +short shop.example.comcurl -sS -o /dev/null -w '%{http_code}\n' https://shop.example.comcurl -sS https://shop.example.com/healthCertificate issuance needs ports 80 and 443 reachable on the new server. If the old host still answers on the production hostname, ACME may validate against the wrong machine. Change DNS first, then save the domain.
Rollback
Section titled “Rollback”Rollback is a DNS change, not a redeploy.
- Point the production hostname back at the old server.
- Re-enable writes on the old app.
- Keep the new app stopped while you compare data.
- Do not restore the old database over the new one. Decide which copy is authoritative and preserve the other for inspection.
If the new app accepted writes after cutover, the old database is stale. Reconcile deliberately before declaring the rollback complete.
After the move
Section titled “After the move”- Confirm outbound webhooks, OAuth callbacks, and payment provider URLs use the production hostname.
- Confirm scheduled tasks run on only one host.
- Add a Shipper backup plan for the database and every writable volume.
- Remove the old host only after a full backup and restore test.
- Delete the temporary DNS record and certificate.
- Review Releases and rollbacks with the new owner, and keep the Recovery runbook close.