> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cloudstic.com/llms.txt
> Use this file to discover all available pages before exploring further.

# cloudstic copy

> Copy snapshot history from one repository into another

Copy snapshots from another repository into this one — to seed a new repository, migrate to a different storage backend, or promote local snapshots into a remote repository.

A copy preserves history. The copied snapshots keep their original creation times, tags, source identity and file metadata, so the destination looks like it always held them rather than like you re-ran a backup today.

## Usage

```bash theme={null}
cloudstic copy -from-store <uri> [options] [snapshot_id...]
```

Both repositories must already exist. `copy` never creates the destination: choosing key slots and encryption is a decision for [`cloudstic init`](/commands/init), not a side effect of a migration.

## Configuring the two repositories

The destination is configured exactly as for every other command — `-store`, `-profile`, `-password`, and the rest.

The source repository gets a parallel set of `-from-*` flags that mirror every flag which locates or unlocks a repository:

```bash theme={null}
cloudstic copy \
  -store s3:dest-bucket/prod -password "destination secret" \
  -from-store local:/tmp/cloudstic-src -from-password "source secret"
```

<Warning>
  The `-from-*` flags read no environment variables. `CLOUDSTIC_PASSWORD` and its
  siblings configure the **destination only**.

  This is deliberate. An ambient variable means "the repository I am operating
  on", and silently applying one to both repositories of a two-repository command
  is how you unlock the wrong one — or believe you did.
</Warning>

For non-interactive use, name the source with `-from-profile`. A profile entry may carry `env://` secret references like any other, which keeps credentials out of your command line:

```bash theme={null}
cloudstic copy -profile remote-prod -from-profile laptop-local
```

The source must always be named explicitly. Unlike `-store`, it has no default.

## Arguments

<ParamField path="snapshot_id..." type="string">
  Optional. Copy only the named source snapshots. Each can be a full hash, any unambiguous prefix, or `latest`. Filters still apply on top, narrowing the set further.

  With no snapshot arguments, every source snapshot is selected.
</ParamField>

## Options

<ParamField path="-from-store" type="string">
  Source repository URI, in the same form as `-store`.
</ParamField>

<ParamField path="-from-profile" type="string">
  Source repository from a named profile. The profile supplies its own store, credentials and secret references.
</ParamField>

<ParamField path="-dry-run" type="boolean" default="false">
  Resolve the selection and report what would be copied, without writing anything.
</ParamField>

<ParamField path="-source" type="string">
  Copy only snapshots of this source URI (for example `local:./Documents`, or a bare type such as `gdrive`).
</ParamField>

<ParamField path="-account" type="string">
  Copy only snapshots of this account.
</ParamField>

<ParamField path="-tag" type="string">
  Copy only snapshots carrying this tag. Repeat the flag to require several tags.
</ParamField>

<ParamField path="-since" type="string">
  Copy only snapshots created at or after this time. Accepts a date (`2026-04-01`) or an RFC 3339 timestamp (`2026-04-01T20:15:03Z`).
</ParamField>

<ParamField path="-allow-copied" type="boolean" default="false">
  Allow copying snapshots that were themselves produced by an earlier `copy`, re-stamping their provenance to the immediate source.
</ParamField>

The full `-from-*` set mirrors the repository flags: `-from-s3-endpoint`, `-from-s3-region`, `-from-s3-access-key`, `-from-s3-secret-key`, `-from-b2-key-id`, `-from-b2-app-key`, `-from-store-sftp-password`, `-from-store-sftp-key`, `-from-store-sftp-known-hosts`, `-from-store-sftp-insecure`, `-from-encryption-key`, `-from-recovery-key`, `-from-kms-key-arn`, `-from-kms-region`, `-from-kms-endpoint`, `-from-disable-packfile` and `-from-prompt`.

Run `cloudstic copy -h` for the authoritative list.

## Examples

<CodeGroup>
  ```bash Migrate to S3 theme={null}
  cloudstic copy \
    -store s3:my-backup-bucket/prod -s3-region us-west-2 \
    -from-store local:/srv/cloudstic
  ```

  ```bash Preview first theme={null}
  cloudstic copy -dry-run \
    -store s3:my-backup-bucket/prod \
    -from-store local:/srv/cloudstic
  ```

  ```bash Between profiles theme={null}
  cloudstic copy -profile remote-prod -from-profile laptop-local
  ```

  ```bash Only one source lineage theme={null}
  cloudstic copy \
    -profile archive -from-profile laptop-local \
    -source local:./Documents
  ```

  ```bash Specific snapshots theme={null}
  cloudstic copy \
    -profile archive -from-profile laptop-local \
    410b18a2 4e5d5487 latest
  ```

  ```bash Only recent history theme={null}
  cloudstic copy \
    -profile archive -from-profile laptop-local \
    -since 2026-01-01
  ```
</CodeGroup>

## Output

Both repositories are named before anything is written, because a mistyped destination is the one mistake a rerun cannot undo:

```plaintext theme={null}
copying from local:/tmp/cloudstic-src
            to s3:dest-bucket/prod

snapshot 410b18a2 of [local:/Users/ada/Documents] at 2026-04-01 18:15:03 +0000
snapshot a1b2c3d4 saved

copied 1 snapshot, skipped 0 snapshots (read 4.2 GiB, wrote 1.1 GiB) in 1m32s
```

Use `-json` for a machine-readable result.

## Rerunning is safe

Each copied snapshot records where it came from, so a second run skips what it already copied without re-reading the source:

```plaintext theme={null}
skipping snapshot 4e5d5487, already copied as snapshot e5f6a7b8

copied 0 snapshots, skipped 1 snapshot (read 0 B, wrote 0 B) in 1ms
```

The same mechanism makes an interrupted copy cheap to resume. Rerun the command and only the unfinished work is repeated — every object already transferred is recognised and skipped.

## What is preserved

| Preserved                          | Reassigned      |
| ---------------------------------- | --------------- |
| Creation time                      | Sequence number |
| Tags                               | Snapshot hash   |
| Source identity and path lineage   |                 |
| File metadata, permissions, xattrs |                 |

Sequence numbers record write order **within** a repository, so they cannot carry across — the source's numbers would collide with history the destination already has. Snapshot hashes change for the reason described below.

## Cost

Copying is much more expensive than an incremental backup, and it is worth understanding why before starting one over a metered link.

Every object reference in a repository is derived from that repository's encryption key. Nothing can be moved verbatim, so each object in the selected snapshots is read and decrypted through the source, then re-encrypted and written to the destination under a new name.

Two things keep this affordable:

* **Deduplication still applies.** Data the destination already holds is recognised and skipped, so copying into a repository that shares content with the source is far cheaper than copying into an empty one.
* **Repeated history is not re-read.** Copying a long run of snapshots costs roughly one repository, not one repository per snapshot, because snapshots that share files share the work.

<Note>
  The source and destination need not share a password, key slots, or encryption at all. Copying between an encrypted and an unencrypted repository works in either direction.
</Note>

## Interaction with retention

A copy re-imports any selected snapshot the destination does not currently have — **including one the destination deliberately forgot**.

For a scheduled copy, pass `-since` with the previous run's start time so that destination retention is not undone on every run:

```bash theme={null}
cloudstic copy \
  -profile remote-prod -from-profile laptop-local \
  -since "$(date -u -d '1 day ago' +%Y-%m-%dT%H:%M:%SZ)"
```

Otherwise, apply [retention](/guides/retention-policies) to the destination after the copy rather than before it.

## Copying a repository into itself

`copy` refuses this. It is not a harmless no-op: each source snapshot would be rewritten as a *new* snapshot carrying provenance, so the history would double. No data would be duplicated, but retention grouping and every snapshot count would be wrong afterwards.

The check does not rely on you spelling the two locations identically — the same repository reached through a symlink, a bind mount, or one bucket under two endpoints is still detected.

## Access requirements

The source is only ever read, so **read-only credentials are sufficient** there. This makes it safe to copy from a repository you do not own or do not want to risk modifying.

If the source permits it, `copy` also places a shared lock there to prevent a concurrent [`prune`](/commands/prune) from removing objects mid-copy. When the source refuses the write, the copy proceeds without that protection. A lock already **held** by another operation still stops the copy, because that means objects are actively being collected.

## Verifying the result

Copied snapshots are ordinary snapshots of the destination afterwards. Verify them the same way you would any others:

```bash theme={null}
cloudstic check -store s3:dest-bucket/prod -read-data
cloudstic list -store s3:dest-bucket/prod
cloudstic restore -store s3:dest-bucket/prod -output ./restored
```

## See Also

* [cloudstic init](/commands/init): Create the destination repository
* [cloudstic check](/commands/check): Verify the destination after copying
* [cloudstic list](/commands/list): Confirm the copied history
* [cloudstic forget](/commands/forget): Apply retention to the destination
* [Go Client API](/advanced/client-api): `Client.CopyFrom` for programmatic use
