Skip to main content
Skip to main content

Migrate from Shared Storage to Local Storage

Shared Storage is deprecated. This guide moves the data of an existing Shared Storage service to a new Local Storage volume and switches your runtime services over to it. The new volume is mounted at the same path the shared storage used (/mnt/<old-hostname>), so your application code and configuration stay untouched.

The migration in short:

  1. Stop everything that writes to the shared storage.
  2. Create the Local Storage service and a temporary migrator service that mounts both storages.
  3. Copy the data from the old mount to the new one.
  4. Disconnect the shared storage from all services and stop it.
  5. Mount the new volume in your runtime services at the old path and deploy.
  6. Verify, then delete the migrator and the shared storage.

Throughout the guide, volume is the hostname of the old Shared Storage service and volume2 the hostname of the new Local Storage service. Replace them with your own.

Before you start

Local Storage is a different kind of storage than the network filesystem it replaces — read its key trade-offs before you migrate. One consequence for the migration: a runtime service mounts at most one Local Storage volume, so if a service mounts several shared storages, copy them into subdirectories of a single volume and point your application at the new paths.

Take a fresh backup of the shared storage before you begin.

1. Stop all writers

Stop every service or process that writes to the shared storage — stop the runtime services in the Zerops GUI, or put your application into a maintenance mode that disables writes. Read-only access can stay up.

Caution

Files that change while they are being copied end up inconsistent on the new volume. Do not skip this step.

2. Create the Local Storage and a migrator service

Import the new Local Storage service together with a temporary fsmigrator service that has both storages mounted: the old shared storage through the import-level mount field, and the new volume through the volume field in its inline zeropsYaml.

zerops-import.yaml
services:
# Pick a free hostname for the new Local Storage service.
- hostname: volume2
type: local-storage:single@1
# Created before the migrator, which mounts it.
priority: 10

# Temporary service used only to copy the data.
- hostname: fsmigrator
type: ubuntu@26.04
zeropsYaml:
zerops:
- setup: fsmigrator
run:
volume:
# Hostname of the new Local Storage service.
hostname: volume2
mount:
# Hostname of the old Shared Storage service.
- volume
# Start the container without deploying any code.
startWithoutCode: true

Import it with the zCLI or in the GUI:

zcli project service-import zerops-import.yaml

Once the migrator is running, its container has the old data at /mnt/volume and the empty new volume at /mnt/volume2.

3. Copy the data

Open a shell in the fsmigrator container — the web terminal on the service detail page in the GUI, or SSH over the Zerops VPN — and copy everything from the old mount to the new one:

cp -a /mnt/volume/. /mnt/volume2/

cp -a preserves permissions, ownership, timestamps and symlinks, and the trailing /. copies the contents of the directory including hidden files.

For large trees, rsync is more robust — it shows progress and can be re-run to resume an interrupted copy:

sudo apt-get update && sudo apt-get install -y rsync
rsync -aH --info=progress2 /mnt/volume/ /mnt/volume2/

Then check that nothing is missing, for example by comparing the sizes and file counts of both trees:

du -sh /mnt/volume /mnt/volume2
find /mnt/volume -type f | wc -l; find /mnt/volume2 -type f | wc -l
Note

du and df can report misleading numbers for the shared storage mount. If the totals differ, compare file counts or run diff -r /mnt/volume /mnt/volume2 before you worry about it.

4. Disconnect and stop the shared storage

On the shared storage service detail page, open Shared storage connections and toggle off every connected runtime service, including fsmigrator. Then stop the shared storage service.

Disconnecting is applied live and does not restart the runtime containers. Keep the service stopped rather than deleted for now — it is your rollback path until the new volume is verified.

5. Mount the new volume in your runtime services

In the zerops.yaml of every runtime service that used the shared storage, add the volume field and set mountPath to the path the shared storage was mounted at, i.e. /mnt/<old-shared-storage-hostname>:

zerops.yaml
zerops:
- setup: app
run:
volume:
# Hostname of the new Local Storage service.
hostname: volume2
# Important: mount at the path of the old shared storage mount,
# i.e. /mnt/{old-shared-storage-hostname}, so the application
# keeps working without any changes.
mountPath: /mnt/volume

Deploy each service with the changed zerops.yaml (zcli push, or trigger your pipeline). The volume is mounted in the new containers at /mnt/volume, exactly where the shared storage used to be. See Mount to a service for the full volume field reference.

If you keep your infrastructure in a zerops-import.yaml or a template, update it as well: replace the shared-storage service with the local-storage:single@1 service and remove the mount field from the runtime services.

6. Verify

Start the runtime services you stopped in step 1 (if they did not start with the deploy) and check that the application works — read an existing file, write a new one, and confirm the results in the Local Storage container, where the volume is mounted at /data.

If something is wrong, you can roll back: remove the volume field from zerops.yaml, deploy, start the shared storage service and reconnect it to the runtime services.

7. Clean up

Once everything works, delete the fsmigrator service and the stopped shared storage service. The Local Storage service keeps the data, and its automated backups take over from the shared storage ones.