# Run your first machine
URL: https://metorial.com/docs/build/sandboxes/quickstart

Create a sandbox machine, run a command in it, and clean up.

---

This guide takes you from nothing to a running machine you can run commands in. It takes about two minutes.

<PageIntro>
  <Learn>
    * How to pick an image and a region
    * How to create and start a machine
    * How to run commands and move files
    * How to delete a machine
  </Learn>

  <Reading title="Before you start">
    * [Install the CLI and sign in](/docs/build/sandboxes#install-the-tools)
    * For the SDK part, create an [API key](/docs/build/api)
  </Reading>
</PageIntro>

## With the CLI [#with-the-cli]

<Steps>
  <Step title="Pick a region">
    List the regions you can run machines in and note an identifier such as `iad1`:

    ```bash
    metorial-admin sandbox regions list
    ```
  </Step>

  <Step title="Pick an image">
    Metorial manages a set of images for common stacks: Debian, Alpine, Node.js, Bun, Deno, Python, Go, Java, PHP, .NET, and images with browser automation preinstalled. Find one and copy its ID (`sbi_...`):

    ```bash
    metorial-admin sandbox images list --owner global --search debian
    ```
  </Step>

  <Step title="Create the machine">
    Machines start right away unless you pass `--start=false`. Without a size, the machine gets the default size.

    ```bash
    metorial-admin sandbox machines create \
      --region iad1 \
      --image-id sbi_your_image_id \
      --name first-machine
    ```

    Save the machine ID from the output and check its status. It moves from `starting` to `running` within a few seconds.

    ```bash
    MACHINE=sma_your_machine_id
    metorial-admin sandbox machines get $MACHINE
    ```
  </Step>

  <Step title="Run a command">
    Anything after `--` runs inside the machine. The CLI exits with the command's exit code.

    ```bash
    metorial-admin sandbox machines shell $MACHINE -- uname -a
    ```

    Leave out the command to open an interactive shell.
  </Step>

  <Step title="Move a file in and out">
    ```bash
    echo "hello from my laptop" | metorial-admin sandbox machines files upload $MACHINE - /tmp/hello.txt
    metorial-admin sandbox machines files cat $MACHINE /tmp/hello.txt
    ```
  </Step>

  <Step title="Delete the machine">
    Deleting a machine also deletes its root disk. Attached [disks](/docs/build/sandboxes/disks) are kept.

    ```bash
    metorial-admin sandbox machines delete $MACHINE
    ```
  </Step>
</Steps>

## With the SDK [#with-the-sdk]

The same flow in code. Machine operations return the machine object; `shell()` and `files()` open a live connection to the machine.

<CodeBlockTabs defaultValue="TypeScript" groupId="language" mode="code">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="TypeScript">
      TypeScript
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="Python">
      Python
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="TypeScript">
    ```typescript
    import { Metorial } from 'metorial';

    let metorial = new Metorial({ apiKey: process.env.METORIAL_API_KEY! });

    let machine = await metorial.sandbox.machines.create({
      region: 'iad1',
      imageId: 'sbi_your_image_id',
      name: 'first-machine'
    });

    while (machine.status != 'running') {
      if (machine.status == 'failed') throw new Error(machine.failure?.message);
      await new Promise(resolve => setTimeout(resolve, 1000));
      machine = await metorial.sandbox.machines.get(machine.id);
    }

    let shell = await metorial.sandbox.machines.shell(machine.id);
    let result = await shell.command('uname -a');
    console.log(result.exitCode, result.stdout);

    let files = await metorial.sandbox.machines.files(machine.id);
    await files.write('/tmp/hello.txt', 'hello from my app');
    console.log(await files.readText('/tmp/hello.txt'));

    shell.close();
    files.close();
    await metorial.sandbox.machines.delete(machine.id);
    ```
  </CodeBlockTab>

  <CodeBlockTab value="Python">
    ```python
    import asyncio
    import os
    from metorial import Metorial

    metorial = Metorial(api_key=os.environ["METORIAL_API_KEY"])

    async def main():
        machine = metorial.sandbox.machines.create(
            region="iad1",
            image_id="sbi_your_image_id",
            name="first-machine",
        )

        while machine.status != "running":
            if machine.status == "failed":
                raise RuntimeError(machine.failure.message)
            await asyncio.sleep(1)
            machine = metorial.sandbox.machines.get(machine.id)

        async with await metorial.sandbox.machines.shell(machine.id) as shell:
            result = await shell.command("uname -a")
            print(result.exit_code, result.stdout)

        async with await metorial.sandbox.machines.files(machine.id) as files:
            await files.write("/tmp/hello.txt", "hello from my app")
            print(await files.read_text("/tmp/hello.txt"))

        metorial.sandbox.machines.delete(machine.id)

    asyncio.run(main())
    ```
  </CodeBlockTab>
</CodeBlockTabs>

<Note>
  In Python, resource calls such as `create` and `get` are synchronous. The live helpers `shell()`, `files()`, and `connect()` are async.
</Note>

## Next steps [#next-steps]

* [Configure a machine](/docs/build/sandboxes/machines): size, environment variables, and startup commands
* [Lifecycle and hibernation](/docs/build/sandboxes/lifecycle): pause and hibernate idle machines automatically
* [Connect with the SDK](/docs/build/sandboxes/connect-sdk): stream output, run background processes, and call ports