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

# Your first instance

> Publish a measured release, create an instance, and send your first verified request.

Deploy the prebuilt [hello-world image](https://github.com/tinfoilsh/tinfoil-containers-hello-world), or bring an image you already have. Both paths use a public config repository, a measured release, and an independently managed **instance**. Instances that use the same config repository belong to one [container project](https://docs.tinfoil.sh/containers/projects.md).

This tutorial reaches a verified HTTP response before changing the running version. Choose the dashboard or CLI instructions at each step; they operate on the same resources.

## Prerequisites

* An organization with Tinfoil Containers enabled in the [dashboard](https://dash.tinfoil.sh), and an organization admin who can create instances and secrets.
* A GitHub account that can create a public repository and run its GitHub Actions workflows.
* The current [Tinfoil CLI](https://docs.tinfoil.sh/containers/cli.md#prerequisites), including for the verified HTTP request if you create the instance in the dashboard.
* For CLI management, an [organization admin key](https://docs.tinfoil.sh/admin/get-admin-key.md). Run `tinfoil login` and check `tinfoil whoami` before creating resources. See [credential precedence](https://docs.tinfoil.sh/containers/cli.md#authenticating) if environment variables are set.

## 1. Create a repo from the template

Open [tinfoil-containers-template](https://github.com/tinfoilsh/tinfoil-containers-template) and choose **Use this template** → **Create a new repository**. Make the new repository **public**. In the commands below, replace `myorg/my-app-config` with its GitHub `owner/repo` name, not the template's name.

The config and release evidence must be public so clients can independently verify the enclave. Your application image can remain [private](https://docs.tinfoil.sh/containers/private-images.md). Never put secret values in the public repository.

Keep both template workflows: `.github/workflows/tinfoil-release.yml` and `.github/workflows/tinfoil-release-publish.yml`. They create the tag, compute its enclave measurement, sign the attestation, and publish the release.

## 2. Review the config

The template contains a complete `tinfoil-config.yml`, including a real image digest. The example below matches [template commit `0eddc32`](https://github.com/tinfoilsh/tinfoil-containers-template/blob/0eddc320b8f328d7a3c057152596934444ac2d75/tinfoil-config.yml). This minimal demo listens on port `8080`, needs no outbound network access or writable storage, and declares one secret you will create before deploying:

```yaml tinfoil-config.yml theme={"dark"}
# Tinfoil Container Configuration
# For updates: edit this file, commit, run the workflow (e.g., v0.0.2)
cvm-version: 0.14.7
cpus: 2
memory: 8192

containers:
  - name: "hello-world"
    image: "ghcr.io/tinfoilsh/tinfoil-containers-hello-world@sha256:c943b1cab16409e2e686ad71deab30364c27421852cdd6b3080df1c7a38331ed"
    env:
      - MESSAGE: "Hello from Tinfoil"
    secrets:
      - GREETING_TOKEN

shim:
  upstream-port: 8080
  paths:
    - /*
```

Use the complete values in your generated repository; do not substitute a placeholder digest or CVM version. The demo reports `MESSAGE` and whether `GREETING_TOKEN` is present, never its value. It does not require a custom domain, GPU, or disk.

### Bringing an existing image

Instead of the demo, edit the config to match your application **before publishing the first release**:

| Check | What to configure |
| - | - |
| Image | Pin `image` to a real `@sha256:` digest. For a private image, configure [registry credentials](https://docs.tinfoil.sh/containers/private-images.md) first. |
| Listen address and port | Have the server listen on `0.0.0.0`, not only loopback. Match `shim.upstream-port` to the application's port and expose the intended `shim.paths`. |
| Outbound access | Outbound access is blocked by default. Declare a [network with an explicit egress policy](https://docs.tinfoil.sh/containers/config-networking.md#defining-networks) and attach the container to it when it must call external services. Include redirect and CDN hostnames in an allowlist. Incoming shim traffic does not require an open egress network. |
| Writable paths | The root filesystem is read-only by default. Declare `tmpfs` scratch paths, or deliberately set `read_only: false`; neither makes data persistent. See [runtime defaults](https://docs.tinfoil.sh/containers/config-runtime.md#security-defaults). |
| Environment and secrets | Put non-sensitive measured settings in `env`; declare secret names under `secrets` and create their values before deploying. Remove the demo's `GREETING_TOKEN` declaration if your app does not use it. |
| Persistent data, if needed | Declare named volume mounts, attach disks on the instance's host, and prepare an [unlock method](https://docs.tinfoil.sh/containers/encrypted-volumes.md#before-deploying-with-storage). Only mounts declaring `key-secret` require an attached disk; optional mounts may stay empty. An attached disk is not necessarily unlocked. |
| Startup | Add a [healthcheck](https://docs.tinfoil.sh/containers/config-runtime.md#healthchecks) if the process needs time to become usable. Its command must exist in your image. |

The remaining steps use the hello-world demo. For your own image, create its declared secrets and use its expected endpoint and response. See the [configuration reference](https://docs.tinfoil.sh/containers/configuration.md) for the schema, or [building an image](https://docs.tinfoil.sh/containers/building-images.md) if you do not have one yet.

## 3. Release a version

First commit any config changes to the default branch. A config pull request must be **merged**, not merely opened or closed, before you release it.

> [tab] Dashboard
>
> 1. Open **Containers** → **Projects**. Under **Available configurations**, choose **Connect GitHub** if needed and grant the Tinfoil GitHub App access to your config repository. Choose **Refresh** after connecting or adding a repository.
> 2. Select your repository under **Available configurations** to open its config editor. If you change a field, choose **Open Pull Request**, then review and **merge** that PR on GitHub. Return to the editor and refresh its status.
> 3. Choose **Release**, enter `v0.0.1`, and choose **Run build**. If the template is unchanged, you can release it without making a cosmetic edit.
> 4. Follow the workflow link and wait for both phases described below to succeed.

> [tab] CLI
>
> Connect the Tinfoil GitHub App to the repository in the dashboard first. If you need to edit the config, download it, edit it locally, and open a PR:
>
> ```bash theme={"dark"}
> tinfoil repo config get myorg/my-app-config --raw > tinfoil-config.yml
> ```
>
> After editing the file:
>
> ```bash theme={"dark"}
> tinfoil repo config pr myorg/my-app-config --file ./tinfoil-config.yml
> ```
>
> Review and **merge** the returned PR on GitHub. An unchanged template needs no PR. Then queue the first release:
>
> ```bash theme={"dark"}
> tinfoil repo build run myorg/my-app-config --version v0.0.1
> ```
>
> Check progress without queuing another run:
>
> ```bash theme={"dark"}
> tinfoil repo build status myorg/my-app-config --version v0.0.1
> tinfoil repo build info myorg/my-app-config
> ```
>
> Also follow the workflow link to verify both phases. Alternatively, with the GitHub CLI authenticated, dispatch directly:
>
> ```bash theme={"dark"}
> gh workflow run tinfoil-release.yml --repo myorg/my-app-config -f version=v0.0.1
> ```

**Queued is not deployable.** Wait for **Tinfoil Release** (`tinfoil-release.yml`, tag creation and dispatch) and **Tinfoil Release - Publish (auto-triggered)** (`tinfoil-release-publish.yml`, measurement, attestation, and publication) to complete successfully. Confirm that GitHub has a published release for `v0.0.1`. A tag alone, a successful dispatch, or a fixed waiting period is not sufficient. If a workflow fails, inspect its logs before proceeding.

## 4. Add the demo secret

Create `GREETING_TOKEN` in the **same Tinfoil organization** as the instance. The demo only checks that it has a nonempty value; use a disposable demo value, not a real credential.

> [tab] Dashboard
>
> Open **Containers** → **Secrets**, choose **Add Secret**, enter `GREETING_TOKEN` and a nonempty value, and save.

> [tab] CLI
>
> Pass the disposable value through stdin:
>
> ```bash theme={"dark"}
> printf '%s' 'hello-world-demo' | tinfoil secret create GREETING_TOKEN --value-file -
> ```
>
> If the secret already exists, use it rather than overwriting a value another instance may depend on. For real credentials, use a protected file or secret-manager input instead of a literal in shell history.

## 5. Create the instance

> [tab] Dashboard
>
> 1. In the config editor's **Latest releases**, find **`v0.0.1`** and choose **Create instance from this release**. This carries the repository and tag into the create form. You can also choose **New instance** from the instances view and select them yourself.
> 2. Name it `hello-world` and confirm the selected repository and **`v0.0.1`** tag match the release you just published.
> 3. Check the image, resources, environment, and `GREETING_TOKEN` secret. Leave debug mode off; the demo needs no custom domain or volume.
> 4. Review **Mark v0.0.1 as the latest release once it's running**, then choose **Create instance**.

> [tab] CLI
>
> ```bash theme={"dark"}
> tinfoil container create hello-world \
>   --repo myorg/my-app-config \
>   --tag v0.0.1
> ```
>
> The command follows deployment progress. To inspect it again:
>
> ```bash theme={"dark"}
> tinfoil container get hello-world
> ```

Wait for **Running**, then copy the instance's domain from the dashboard or CLI output. The first instance creates its project automatically if that repository does not already have one. A source repository alone is not a running project or instance.

Marking the tag as the latest release defaults to enabled (`--mark-latest=false` disables it). After a non-debug instance is running, Tinfoil can mark its tag as the repository's latest GitHub release when the GitHub App is connected and the repository is active. This is a **repository-wide** change: consumers that follow GitHub's latest release can be affected even though other instances are not updated. This tutorial pins requests to the exact tag.

## 6. Make a verified request

This step uses [beta V3 attestation verification](https://docs.tinfoil.sh/resources/feature-status.md) through the CLI.

Replace `hello-world.myorg.containers.tinfoil.dev` with the actual domain from step 5, without `https://`. Replace the repository with your own and keep the `@v0.0.1` pin:

```bash theme={"dark"}
tinfoil http get \
  -e hello-world.myorg.containers.tinfoil.dev \
  -r myorg/my-app-config@v0.0.1 \
  https://hello-world.myorg.containers.tinfoil.dev/
```

The CLI verifies attestation against your published release and pins the enclave's TLS identity before sending the request. The untouched demo returns:

```text theme={"dark"}
MESSAGE: Hello from Tinfoil
GREETING_TOKEN: present
```

If you changed `MESSAGE` before publishing, expect that value instead. A verification failure is not a reason to disable verification: check the domain, repository, tag, release publication, and that debug mode is off. For application clients, use a [Tinfoil SDK](https://docs.tinfoil.sh/containers/connecting.md).

A direct `curl` to the public domain only checks connectivity; it does not verify the enclave. Do not use it to send sensitive data.

## 7. Update only after the first request works

1. Edit `MESSAGE` in the config to `A new message!`. Open a config PR and **merge it** into the default branch.

2. Release **`v0.0.2`**, using **Release** in the dashboard or:

   ```bash theme={"dark"}
   tinfoil repo build run myorg/my-app-config --version v0.0.2
   ```

3. Wait for both workflow phases to succeed and for `v0.0.2` to be published.

4. Choose **Update** on `hello-world` and select `v0.0.2`, or run:

   ```bash theme={"dark"}
   tinfoil container update hello-world --tag v0.0.2
   ```

5. After the update completes, repeat the verified request from step 6 with **`@v0.0.2`**. Expect `MESSAGE: A new message!` and `GREETING_TOKEN: present`.

The demo updates blue/green: the current enclave keeps serving until the replacement is healthy. Publishing alone never changes a running instance. To inspect a candidate before switching traffic, use [Hold for review](https://docs.tinfoil.sh/containers/hold-for-review.md). For downtime, current-to-target settings, and rollback behavior, see [Updating](https://docs.tinfoil.sh/containers/updates.md).

Updates inherit the project's hold default. If it is enabled, verify the candidate at its review URL and **Promote** it before expecting `v0.0.2` at the production URL; or explicitly choose `--hold=false` for this update.

## Cleanup

Choose **Stop** in the dashboard or run `tinfoil container stop hello-world` when you no longer need the demo running. Stopped instances keep their saved configuration, not their memory. Use **Deploy** to boot it again or **Delete** to remove it permanently; see [Instance lifecycle](https://docs.tinfoil.sh/containers/lifecycle.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.