Traducciones al Español
Estamos traduciendo nuestros guías y tutoriales al Español. Es posible que usted esté viendo una traducción generada automáticamente. Estamos trabajando con traductores profesionales para verificar las traducciones de nuestro sitio web. Este proyecto es un trabajo en curso.
Create a Linode account to try this guide with a $ credit.
This credit will be applied to any valid services used during your first  days.

The ability to migrate a virtual machine’s disk image across cloud providers is essential to cloud architecture durability and a key component of multi-cloud portability. There are numerous methods of migrating a disk image, several of which can be found in our documentation library: Migrate to Linode

This tutorial includes steps for deploying a disk image to a new compute instance using cloud-init and the Ubuntu 24.04 LTS distribution. Cloud-init is an industry standard configuration tool that helps automate the configuration of new compute instances upon initial boot.

The method in this guide uses Object Storage on Akamai Cloud for disk image storage, and provides a custom cloud-init configuration compatible with Akamai’s Metadata service.

How It Works

Cloud-init configuration files act as a setup script that a compute instance reads during the initial boot process. During this process, metadata or other user-provided data (i.e. a custom disk image) is sourced and applied to the instance.

The cloud-init configuration provided in this guide uses the Linux pivot_root function to swap the standard root file system for the new instance’s RAM disk, where it can temporarily store a custom disk image sourced from an object storage bucket via signed URL. The original root disk is remounted upon reboot.

In order for the cloud-init script to function, the RAM disk of your new instance must be larger than the provided custom disk image file. Once the Linode is booted and the disk image is successfully loaded, the instance can be optionally downsized to a smaller plan with less RAM.

Before You Begin

The following prerequisites are required for completing the steps in this guide:

  • An Akamai Cloud account with permissions in place for managing Object Storage and Compute Instances

  • The Linode CLI installed and configured for use with your Akamai Cloud account

  • An Object Storage bucket and valid access key for storing and accessing your disk image file

  • The s3cmd command line utility installed and configured to interact with your Object Storage bucket and region

Disk Image Requirements

There are several requirements that must be met for your disk image to successfully run on a Linode instance. See our documentation for guidance on setting up or obtaining an image file. The process for acquiring a disk image may vary depending on the cloud provider you are migrating from (i.e. AWS, Azure, Google Cloud, etc.).

  • Image size: The total size of your disk image should be lower than the RAM disk size of your new compute instance. For example, if your disk image file is 12GB, your new instance should have 16GB of RAM available. If necessary, your new instance can be resized to a smaller plan size afterwards.

  • Linux kernel: It is recommended to use a platform-agnostic kernel such as the distro-provided kernel with GRUB 2 as the bootloader - rather than a provider-optimized kernel (i.e. Amazon Linux kernels). This helps avoid any platform-based dependencies or compatibility issues.

  • Disk format and partitions: The disk must use the ext3 or ext4 file system in order to be compatible. The cloud-init configuration provided in this guide only supports non-partitioned disk images.

  • File type: Your disk image can be either in uncompressed .img or compressed .gz (gzip) format. At the time of this writing, .iso image files are not supported.

Note
The provided cloud-init configuration uses a file’s metadata to gather information and does not look for specific file extensions.

If you are using a disk image that requires direct disk to boot (i.e. an image with partitioned disks or a Master Boot Record, or MBR), you may need to take additional steps to configure your system before image creation or after deployment for full compatibility with the Akamai Cloud platform. See our guide on Installing a Custom Distribution for guidance and configuration options for a direct disk image.

Upload a Disk Image to Object Storage

Create an Object Storage Bucket
If you haven’t done so already, create an Object Storage bucket in which to store your disk image.

The Object Storage item upload limit is 5GB. This is the same as our Custom Image upload limit. If using an uncompressed or compressed disk image larger than 5GB, you must use an alternative tool such as s3cmd when uploading your image file.

You can optionally compress your disk image file using the following gzip command. The -9 flag regulates the speed of compression by running at a slower speed while maintaining the highest quality:

gzip -9 exampleimage.img

Upload Using Cloud Manager

  1. While logged into Cloud Manager, select Object Storage from the side bar.

  2. Select your bucket.

  3. Drop and drag your disk image file, or click Browse Files to select the file from your local machine.

Upload Using the Linode CLI

  1. Confirm access to your account by viewing a list of available buckets. If prompted, specify your bucket region (i.e. us-east-1):

    linode-cli obj ls
    2025-07-16 18:37  bucket1
  2. Upload your disk image file to your bucket, replacing exampleimage.img with the your disk image and bucket1 with your bucket name:

    linode-cli obj put --acl-public exampleimage.img bucket1
  3. Confirm the upload using the ls function:

    linode-cli obj ls bucket1

Upload Using s3cmd

  1. Confirm your s3cmd utility is configured to interact with your destination bucket by listing the available buckets:

    s3cmd ls
    2025-07-16 18:37  s3://bucket1
  2. Upload your disk image using the s3cmd put command:

    s3cmd put exampleimage.img s3://bucket1/

    s3cmd uses multi-part uploading for objects over a certain size. Allow this process to complete for the upload to finish:

    upload: '/path/to/image/exampleimage.img' -> 's3:/bucket1/exampleimage.img'  [part 1 of 80, 15MB] [1 of 1]
    15728640 of 15728640   100% in    1s    10.46 MB/s  done
    upload: '/path/to/image/exampleimage.img' -> 's3://bucket1/exampleimage.img'  [part 2 of 80, 15MB] [1 of 1]
    15728640 of 15728640   100% in    1s    13.66 MB/s  done
    upload: '/path/to/image/exampleimage.img' -> 's3:/bucket1/exampleimage.img'  [part 3 of 80, 15MB] [1 of 1]
    15728640 of 15728640   100% in    1s    14.60 MB/s  done
    ...
  3. Confirm presence of the disk image file in your bucket:

    s3cmd ls s3://bucket1
    2025-07-16 22:11  26289897472  s3://bucket1/exampleimage.img

Create a Signed URL for the Disk Image

Signed URLs create a web link to specific objects in a bucket while limiting permission and applying a defined time limit for access.

Use the following command to create a signed URL for your disk image with the Linode CLI. Replace bucket1 with your bucket name, and exampleimage.img with the name of your disk image. Replace 1000 (~16 minutes) with the number of seconds you wish to allow access to your object:

Linode CLI
linode-cli obj signurl bucket1 exampleimage.img +1000

Alternatively, if you wish to create a signed URL for your disk image using s3cmd, use the following command, replacing the same values as above:

s3cmd
s3cmd signurl s3://bucket1/exampleimage.img +1000

This generates output in the form of a signed URL. Note that the below URL has been scrubbed of identifying information and is invalid:

https://us-east-1.linodeobjects.com/bucket1/exampleimage.img?AWSAccessKeyId=ABCNLJNAESNMPEE8N123&Signature=2cxU6qOKE2%2BjADFNRrdb1lMWubI%3D&Expires=1752849789

Save your signed URL somewhere secure so that it can be used in the cloud-init configuration in the following section.

Deploy an Instance Using Your Disk Image

  1. While in Cloud Manager, select Linodes from the side bar, and click Create Linode.

  2. Use the following parameters as guidelines when creating your Linode instance:

    • Region: The same region as your Object Storage bucket (optional).
    • Linux Distribution: Ubuntu 24.04 LTS
    • Linode Plan: Select a compute plan with a larger RAM disk than your disk image size. For example, if your disk image is 25GB, select a Linode plan with 32GB RAM. The cloud-init script used in this guide swaps out the root boot disk and uses your new instance’s RAM disk to temporarily house your uploaded disk image. After your new instance is up and running, you can optionally downsize your Linode to a smaller plan with less RAM.
    Linode Plan Size Minimum
    A Linode plan with a minimum of 16GB RAM is recommended for the cloud-init configuration to successfully run.
  3. Under Add User Data, insert the following cloud-init config file contents in the User Data field. Make sure the config appears exactly as displayed below with no leading or trailing spaces.

    In the line mount none /tmp/tmproot -t tmpfs -o size=30G (row 9), adjust the temporary disk size in accordance with your chosen RAM disk size. Make sure to specify a smaller tmpfs (temporary file system) than your total RAM. It must be large enough to hold the cloud-init Ubuntu OS and disk image coming from Object Storage, and small enough that it doesn’t use up all the RAM available. Generally this means a tmpfs 2-4GB smaller than your total plan RAM size.

    For example, if your Linode plan has 64GB of RAM and your disk image size is between 30GB and 60GB (i.e. 48GB), change the RAM disk size to 60G. This allows for some extra room between your disk image size and the total RAM disk. The 30G example in the script assumes your disk image is large enough to require a plan size of 32GB.

    Replace the SIGNED_URL placeholder in the wget command (row 13) with the signed URL for your disk image generated in the previous section:

    File: cloud-init config
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    
    #cloud-config
    write_files:
        - path: /run/scripts/test-script.sh
          content: |
            #!/bin/bash
            echo 'making directory /tmp/tmproot' >&2
            mkdir /tmp/tmproot
            echo 'mounting tmpfs to /tmp/tmproot' >&2
            mount none /tmp/tmproot -t tmpfs -o size=30G
            echo 'changing directory to /tmp/tmproot' >&2
            cd /tmp/tmproot
            echo 'downloading signed image from object storage' >&2
            wget "SIGNED_URL" -O /tmp/tmproot/signed_image.img
            filename="signed_image.img"
            if [[ $(file -b signed_image.img) == *gzip* ]]; then
                echo 'detected a compressed file gzip' >&2
                mv signed_image.img signed_image.img.gz
                filename="signed_image.img.gz"
            fi
            echo 'running telinit 2 to switch to runlevel 2' >&2
            telinit 2
            echo 'making directories in /tmp/tmproot' >&2
            mkdir /tmp/tmproot/{proc,sys,usr,var,oldroot}
            echo 'copying files to /tmp/tmproot' >&2
            cp -ax /{bin,etc,mnt,sbin,lib} /tmp/tmproot/
            echo 'copying directories to /tmp/tmproot/usr' >&2
            cp -ax /usr/{bin,sbin,lib} /tmp/tmproot/usr/
            echo 'copying directories to /tmp/tmproot/var etc.' >&2
            cp -ax /var/{account,empty,lib,local,lock,nis,opt,preserve,run,spool,tmp,yp} /tmp/tmproot/var/
            echo 'copying directories to /tmp/tmproot/dev' >&2
            cp -a /dev /tmp/tmproot/dev
            echo 'copying directories to /tmp/tmproot/run' >&2
            cp -ax /run /tmp/tmproot/
            echo 'copying directories to /tmp/tmproot/lib64 and /tmp/tmproot/usr/lib64' >&2
            cp -ax /lib64 /tmp/tmproot/
            cp -ax /usr/lib64 /tmp/tmproot/usr/
            echo 'mount make-rprivate /' >&2
            mount --make-rprivate /
            echo 'pivoting root to /tmp/tmproot' >&2
            pivot_root /tmp/tmproot/ /tmp/tmproot/oldroot
            echo 'mounting none /proc -t proc' >&2
            mount none /proc -t proc
            echo 'mounting none /sys -t sysfs' >&2
            mount none /sys -t sysfs
            echo 'mounting none /dev/pts -t devpts' >&2
            mount none /dev/pts -t devpts
            echo 'moving mounts from /oldroot to /' >&2
            for i in dev proc sys run; do mount --move /oldroot/$i /$i; done
            echo 'telinit u to switch to runlevel 1' >&2
            telinit u
            echo 'systemctl isolate rescue.target' >&2
            systemctl isolate default.target
            echo '#!/bin/bash' > /tmp/test-script-cont.sh
            chmod +x /tmp/test-script-cont.sh
            echo 'exec >/dev/ttyS0 2>&1' >> /tmp/test-script-cont.sh
            echo 'cd /' >> /tmp/test-script-cont.sh
            echo 'echo "started sub script"' >> /tmp/test-script-cont.sh
            echo 'pids=$(fuser -vm /oldroot | xargs | sed "s/kernel //") >> /tmp/test-script-cont.sh'
            echo 'echo "got pids: $pids"' >> /tmp/test-script-cont.sh
            echo 'kill $pids' >> /tmp/test-script-cont.sh
            echo 'echo "killed pids"' >> /tmp/test-script-cont.sh
            echo 'umount /oldroot' >> /tmp/test-script-cont.sh
            echo 'echo "umounted /oldroot"' >> /tmp/test-script-cont.sh
            # if filename matches gzip, decompress it
            if [[ $filename == "signed_image.img.gz" ]]; then
                echo 'echo "detected a compressed file gzip"' >> /tmp/test-script-cont.sh
                echo 'which gzip' >> /tmp/test-script-cont.sh
                echo 'gzip -cd signed_image.img.gz | dd of=/dev/sda status=progress' >> /tmp/test-script-cont.sh
            else
                echo 'dd if=signed_image.img of=/dev/sda status=progress' >> /tmp/test-script-cont.sh
            fi
            echo 'echo "dd finished about to reboot"' >> /tmp/test-script-cont.sh
            echo 'reboot' >> /tmp/test-script-cont.sh
            nohup bash -c /tmp/test-script-cont.sh &
          permissions: '0755'
    
    runcmd:
      - [ bash, "/run/scripts/test-script.sh" ]
  4. Finish selecting your Linode instance parameters, and click Create Linode. Depending on the contents and configuration of your disk image, this may take some time to complete. You can monitor the status of your instance’s deployment using the Lish console.

  5. Once fully booted, log into your new instance using the user and credentials from your original disk image, replacing IP_ADDRESS with the IPv4 of your Linode instance:

    ssh USER@IP_ADDRESS
  6. Complete the steps in our Set Up and Secure a Linode guide to secure your instance, including updating your system, adding a limited sudo user, hardening SSH access with public key authentication, and configuring a firewall.

Disk Name
Once the process is complete, the Linode instance’s configuration displays the disk name as “Ubuntu 24.04” in Cloud Manager upon first boot regardless of the incoming operating system used by the origin disk. The actual operating system on the instance will match that of the incoming disk image. The configuration can be renamed within the Cloud Manager interface.

Resizing the Instance

Since the cloud-init script initially requires a larger amount of RAM, you may find yourself with more RAM than your use case requires. You can optionally resize your instance to a smaller Linode plan size to reduce unnecessary compute resources and lower billing cost. When downsizing, remember to select a Linode plan with enough CPU, RAM, and storage to support your original workload.

See our Resize a Linode guide for instructions on how to change your Linode plan.

More Information

You may wish to consult the following resources for additional information on this topic. While these are provided in the hope that they will be useful, please note that we cannot vouch for the accuracy or timeliness of externally hosted materials.

This page was originally published on


Your Feedback Is Important

Let us know if this guide was helpful to you.


Join the conversation.
Read other comments or post your own below. Comments must be respectful, constructive, and relevant to the topic of the guide. Do not post external links or advertisements. Before posting, consider if your comment would be better addressed by contacting our Support team or asking on our Community Site.
The Disqus commenting system for Linode Docs requires the acceptance of Functional Cookies, which allow us to analyze site usage so we can measure and improve performance. To view and create comments for this article, please update your Cookie Preferences on this website and refresh this web page. Please note: You must have JavaScript enabled in your browser.