Inconsistency? incus copy vs incus launch

I’m using incus 6.0.4. When I launch a new container, I have two ways to set the config:

incus launch images:ubuntu/24.04/cloud test1 -c user.greeting="hello"

incus launch images:ubuntu/24.04/cloud test2 <<EOS
config:
  user.greeting: hello
EOS

incus config get test1 user.greeting   # => hello
incus config get test2 user.greeting   # => hello

But with incus copy, only the first of these works:

incus copy test1 test1f -c user.greeting="bonjour"

incus copy test2 test2f <<EOS
config:
  user.greeting: bonjour
EOS

incus config get test1f user.greeting  # => bonjour
incus config get test2f user.greeting  # => hello

Why does this matter? Well, sometimes I want to copy a container and replace some rather chunky blocks of config, in particular cloud-init.network-config and cloud-init.user-data. I have a neat way of passing these overrides to incus launch, but not to incus copy.

There’s incus config edit, but unfortunately it requires the full set of keys (including volatile ones) to be passed:

incus config edit test2f <<EOS
config:
  user.greeting: bonjour
EOS
# => Error: Volatile idmap keys can't be deleted by the user

So I guess incus copy accepting input on stdin wouldn’t be all that useful, unless there were a “merge” mode for config updates (which wouldn’t give you a way to unset a value)

I’m not sure if this is relevant to your copy-based workflow, but my approach to those chunky blocks of cloud-init config is to put them in files and add them with incus config set to not-yet-started instances.

# create a container with a profile…
incus create images:alpine/3.22/cloud test123 --profile testprofile
# …or an ad-hoc config
incus create images:alpine/3.22/cloud --no-profiles test123 < config.yml

# add cloud-init config from a file…
incus config set test123 cloud-init.vendor-data - < init.yml
# …or perhaps a heredoc
incus config set test123 cloud-init.vendor-data - <<EOS
#cloud-config
user: testuser
timezone: Africa/Casablanca
EOS

# then start the container and wait for cloud-init
incus start test123 && incus exec test123 -- cloud-init status --wait

Maybe doing some similar incus config sets after the copy does what you want?

It would. What I’m currently doing is:

(
  incus copy $SRC $DEST \
    -c cloud-init.network-config="$(cat <&3)" \
    -c cloud-init.user-data="$(cat <&4)"
) 3<<END1 4<<END2
version: 2
ethernets:
...
END1
#cloud-config
chpasswd: { expire: False }
...
END2

What I wanted was something vaguely atomic: if the copy was made, then it definitely has a different config to the original. But on balance, your version is clearer.

It is slightly concerning that neither the documentation nor the built-in help for incus config set says that you can pass a value on stdin, if the placeholder value is “-”. (And presumably, nobody ever wants to set a configuration value to the literal value “-”?)

I think that I can safely guess that this feature was simply not picked during the last iteration of documentation writing. And can be added with a PR (easy task) if someone wants to pick up. I think the first step is to file an issue on Github, then let someone pick it up.

1 Like

Seems to me that’s what profiles are for – encapsulating a bunch of configuration to be applied or replaced in a single hit.

❯ incus profile create prof1
Profile prof1 created
❯ incus profile set prof1 user.country="Benin"
❯ incus create images:alpine/3.22 alp1 --profile prof1 < config.yml
Creating alp1
❯ incus config get alp1 user.country --expanded
Benin

# copy with a different profile

❯ incus profile create prof2
Profile prof2 created
❯ echo "Togo" | incus profile set prof2 user.country -
❯ incus copy alp1 alp2 --profile prof2
❯ incus config get alp2 user.country --expanded
Togo

# or copy to a different project that also defines the profile

❯ incus project create proj
Project proj created
❯ incus profile create prof1 --project proj
Profile prof1 created
❯ incus profile set prof1 user.country="Ghana" --project proj
❯ incus copy alp1 alp1 --target-project proj
❯ incus config get alp1 user.country --expanded --project proj
Ghana

Not in this case: the config I want to apply is specific to the container in question, in particular the static IP address in cloud-init.network-config. Making a separate profile for each container doesn’t make sense.