Does "incus create" have a way to upload specified files when creating a new container?

I have a some standard files that I like to upload to new containers/VMs that go in specific directories. (e.g. /etc/profile.d/ )

Is there a way during “incus create” to have those files sent to the container as the container is being built (not after)?

I see I can use “incus file pushafter the container is created as part of a separate script

I see I can copy the entirety of the file into a config_foo.yaml file and use “incus create ... < config_foo.yaml

But what I’m looking for is some “put this local file in this container’s spot” command as part of the incus create process. For example: If I have files a,b,c locally in /path/to/local/ and I want them to be on the newly created container, are there commands like

config:
    user.user-files:
       - /path/to/local/a  /remotepath_a/a
       - /path/to/local/b  /remotepath_b/b
       - /path/to/local/c  /remotepath_c/c

or

incus create --cp_file=/path/to/local/a:/remotepath_a/a

?

Thanks!

Have you thought about using cloud-init? That would still copy the files over after the instance first boots but at least you can use the same strategy in all environments.

Another option is to build your own images. I can think of a few other ways but they get a bit esoteric.

I don’t think there is a built in way of doing this with ‘incus create’.

I am using cloud-init, as far as I know there are no ways using cloud-init to specify a path to files for the content of files to be written to the newly created container. All the examples I’ve found require the content to be written into the .yaml file.

I guess so. I usually baee64 encode them. It has been a while since I had this issue. This is good enough for small files.

For large binaries, I build my own images with distribuilder.

1 Like

I think I might try a deploy token for the files. This way I’ll have them in a RCS that can pull across all containers. I think the commands run after networking is setup, so I should be able have a config.yaml something like …

user.user-data: |
    #cloud-config
    packages:
      - curl
    runcmd:
      - mkdir -p /path/to/adir/
      - curl --header "PRIVATE-TOKEN: <my_deploy_token>" \
             --url "https://<gitlab_url_here>/<namespace>/<project>/-/raw/<branch>/path/on/repo/a" \
             --output /path/to/adir/a
      - chown 600 /path/to/adir/a

Does that seem reasonable?

Yeah. That should work. I think there is an edge case if you are trying to copy the files to a user’s home directory. If you run into an issue there, then we can can figure that out too. I think it requires a but more yaml.

I think I figured it out

  • Deploy Keys: uses ssh/git commands, requires installing git on the container

To use Deploy Keys you get a username/token as follows

runcmd:
    - git clone https://TOKEN-USER:TOKEN-PASSWD@gitlab...../REPO.git  /tmp/
    - cp /tmp/REPO/a /path/to/adir/a
    - cp /tmp/REPO/b /path/to/bdir/b
  • Project Access Tokens: Uses HTTPS, requires installing curl (or the equivalent) on the container

To use a Project Access Token you use the API via HTTP

runcmd:
  - curl https://path_to_git_api_for_file_a --output /path/to/adir/a

I didn’t want to pull over an entire git repo and didn’t want to install git and all the dependencies on the container, so I opted for the API key and curl.

I didn’t want to leave private keys on the container or in the yaml file so I tested both --environment-file=conf_file.env and --config environment.MYVARIABLE for files that are free of keys. Both worked (see below)

Some notes from testing:

runcmd: source FILENAME

and

runcmd: . FILENAME

does not work in launching an incus cloud -enabled container

Both “runcmd source” and “runcmd .” generated the error

cloud-init: /var/lib/cloud/instance/scripts/runcmd: 4: source: not found

To get around this I pulled environment variables from /proc/1/environ

  runcmd:
    - eval $(cat /proc/1/environ | tr '\0' '\n')
    - shutdown -r now

Those variables persist with reboots so I ran incus config unset CONTAINER environment.secret_value and ran a shutdown -r now at the end of the runcmd commands.

Here’s the full script and redacted config.yaml. I used this to add security settings like notification on the activation of any interactive shell (e.g. login) and recording commands in real-time for auditing.

incus launch images:debian/13/cloud web01 --config environment.secret=mysecret \
   --environment-file=gitlab.env --profile BridgeStuff --profile DriveStuff 
   --profile cloud_disable_ipv6 < configA.yaml \ 
   && incus config unset web01 environment.GITLAB_PROJECT_TOKEN \
   && incus config unset web01 environment.mysecret

with an abbreviated configA.yaml as

config:
  user.network-config: |
    version: 2
    ethernets:
      eth0:
        dhcp4: false
        addresses:
          - A.B.C.D/E
        routes:
          - to: 0.0.0.0/0
            via: A.B.C.1
        nameservers:
          addresses:
            - F.G.H.I
            - F.G.H.J
  cloud-init.user-data: |
    #cloud-config
    users:
      - name: NotDebian
        groups: sudo
        shell: /bin/bash
        ssh_authorized_keys:
          - ssh-rsa ssh-rsa REDACTED
    packages:
      - vim
      - curl
      - msmtp
    runcmd:
      - eval $(cat /proc/1/environ | tr '\0' '\n')
      - apt-get update
      - apt-get upgrade -y
      - mkdir /etc/profile.d/bashrc.d
      - |
        curl --silent --header "PRIVATE-TOKEN: $GITLAB_PROJECT_TOKEN" \
             --url "https://REDACTED/api/v4/projects/$GITLAB_PROJECT_ID/repository/files/FILEPATH_REDACTED/raw?ref=main" \
             --output /etc/profile.d/bashrc.d/Z99_record_commands.sh
      - chmod a+x /etc/profile.d/bashrc.d/Z99_record_commands.sh
      - |
        curl --silent --header "PRIVATE-TOKEN: $GITLAB_PROJECT_TOKEN" \
             --url "https://REDACTED/api/v4/projects/$GITLAB_PROJECT_ID/repository/files/FILEPATH_REDACTED/raw?ref=main" \
             --output /etc/profile.d/bashrc.d/Z98_notify_on_login.sh
      - chmod a+x /etc/profile.d/bashrc.d/Z98_notify_on_login.sh
      - |
        curl --silent --header "PRIVATE-TOKEN: $GITLAB_PROJECT_TOKEN" \
             --url "https://REDACTED/api/v4/projects/$GITLAB_PROJECT_ID/repository/files/FILEPATH_REDACTED/raw?ref=main" \
             --output - >> /etc/bash.bashrc
      #reboot now and if "incus config unset ..." has been run, the private vars are gone. 
      - shutdown -r now
2 Likes