Docs

Build your own setup screen

Your own setup screen is the Build it into your product option of Deployment UX: the customer starts the deployment from a page in your product and never sees the deployment portal. One API call creates the deployment and returns the setup step for the cloud and method the customer picked.

Example UI /Not deployed

Document processing

Settings · Deploy in your cloud

Not deployed

Deploy in your cloud

Run document processing in your own AWS account, next to your files.

DeploySend to an admin

What the customer does:

  1. Opens Deploy in your cloud in your product, picks a cloud and a region, and clicks Deploy.
  2. Runs the setup step your screen shows, with their own cloud credentials. On AWS that's one click into the AWS console.
  3. Comes back to your screen, which follows the deployment until it's ready.

What your backend does:

  1. Creates a setup link for the customer.
  2. Creates the deployment with the link's token. The response holds the setup step.
  3. Saves the deployment ID, and reads the deployment's status from then on.

What your screen can launch

The response's installation object depends on the setup method (setupMethod) you ask for:

Setup methodPlatformsWhat your screen shows
cloudformationAWSA button that opens installation.launchUrl: the AWS console's create-stack page, every parameter filled in.
terraformAWS, Google Cloud, Azure, Kubernetesinstallation.terraform.hcl to save as main.tf, and the commands that apply it.
helmKubernetesinstallation.helm.values to save as values.yaml, and installation.helm.command.
cliAWS, Google Cloud, AzureThe setup CLI: installation.cli.installCommand, cli.configuration to save as deployment.toml, and cli.command.

Only CloudFormation opens with one click. The others are commands the admin copies and runs.

Send these customers to the deployment portal instead, from the same setup link:

  • Google Login. Google's sign-in returns only to the portal's domain, so your own page can't run it. Offer Terraform or the CLI on your page, or send Google Cloud customers to the portal.
  • Linux servers. The join command for your customers' machines comes from the portal.
  • Everything you don't build. Any cloud or method your screen doesn't offer.

Before you start

  • Publish a release with alien release, and wait until the setup packages for your methods are built.
  • Set up the API client from Add a Deploy button. The snippets import alien and PROJECT from it.

Every Alien call runs on your server. The browser only talks to your backend.

Server. When the customer clicks Deploy, create a setup link for their organization. Its setup token can create deployments for this one customer and nothing else.

POST /v1/deployment-groups/setup-links

lib/setup-link.ts
import { alien, PROJECT } from "./alien"

export async function createSetupLink(org: { id: string; slug: string }) {
  const link = await alien.setupLinks.create({
    project: PROJECT,
    externalId: org.id, // your immutable organization ID, from the session
    name: org.slug,
    maxDeployments: 1,
    setupItems: [{ item: "deployment", required: true }],
  })
  return { setupToken: link.token, portalUrl: link.deploymentLink }
}
  • On click, not on render. Each call mints a new token.
  • Organization from the session. Never take it from the request body.
  • Keep portalUrl for the customers you send to the portal.

Map customers to deployments explains the external ID. Sizes and a network choice go on this same link: see Sizes and networks.

Create the deployment

Server. Create the deployment with the setup token, not your API key. setupHandoff: "await-registration" tells Alien the customer's setup step creates the infrastructure and reports back when it's done. installation asks for the setup step.

POST /v1/deployments

lib/create-deployment.ts
import { Alien } from "@alienplatform/platform-api"
import { PROJECT } from "./alien"

export type SetupChoice = {
  platform: "aws" | "gcp" | "azure"
  method: "cloudformation" | "terraform" | "cli"
  region: string
}

export async function createDeployment(setupToken: string, name: string, choice: SetupChoice) {
  // Acts for this one customer
  const customer = new Alien({ apiKey: setupToken })

  return customer.deployments.create({
    name,
    project: PROJECT,
    platform: choice.platform,
    setupItem: "deployment",
    setupMethod: choice.method,
    setupHandoff: "await-registration",
    installation: { region: choice.region },
  })
}

The response for CloudFormation:

POST /v1/deployments
{
  "deployment": { "id": "dep_0c29fq4a2yjb7kx3smwdgxlc", "status": "pending", "setupMethod": "cloudformation" },
  "token": "…",
  "installation": {
    "method": "cloudformation",
    "region": "us-east-1",
    "stackName": "a3f9c21b-customer-1",
    "launchUrl": "https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/create/review?…"
  }
}
  • token is the deployment token: the credential for this one deployment. The setup step keeps it, so the setup link can expire without breaking the deployment.
  • Kubernetes. Helm takes installation.namespace. Terraform on Kubernetes also needs basePlatform (aws, gcp or azure).
  • Package still building. Right after a release, the call fails with 409 INSTALLATION_NOT_READY. Show "Preparing the setup" and try again in a minute.

Show the setup step

Server. Your route creates the link and the deployment, and saves the deployment ID before it returns:

app/api/deploy/route.ts
export async function POST(req: Request) {
  const org = await currentOrg(req) // from the session, never from the body
  const choice = parseSetupChoice(await req.json()) // allow only the clouds and methods you offer
  const { setupToken } = await createSetupLink(org)
  const created = await createDeployment(setupToken, org.slug, choice)
  await db.orgs.update(org.id, { deploymentId: created.deployment.id })

  const { installation, token, managementExternalId } = created
  return Response.json({ installation, token, managementExternalId })
}

Browser. For CloudFormation, open the tab before you wait for your backend, so popup blockers allow it:

components/deploy-button.ts
export async function onDeployClick(choice: SetupChoice) {
  const tab = choice.method === "cloudformation" ? window.open("about:blank", "_blank") : null
  const res = await fetch("/api/deploy", { method: "POST", body: JSON.stringify(choice) })
  if (!res.ok) { tab?.close(); throw new Error(await res.text()) }
  const setup = await res.json()
  if (tab) tab.location.href = setup.installation.launchUrl
  return setup // Terraform and CLI: render the steps on your page
}

For Terraform, show installation.terraform.hcl as main.tf, then the commands with the response's values:

Run Terraform
export TF_VAR_token='<token>'
export TF_VAR_management_external_id='<managementExternalId>' # AWS only
terraform init && terraform apply

For the setup CLI, show cli.installCommand, cli.configuration as deployment.toml, and cli.command. cli.commandVariants holds the same commands for linux, mac and windows.

  • The admin sees the token. It's in the launch URL and in the commands. That's expected: it acts only on this deployment. Show it only to signed-in members of this organization.
  • Not the admin. The person who clicks often can't create IAM roles. Offer Send to an admin with the setup link (portalUrl): the portal walks the admin through the same deployment.

Come back to it

Example UI /Waiting for setup

Document processing

Settings · Deploy in your cloud

Waiting for setup

Finish the setup in AWS

The setup hasn’t started in your AWS account yet. We’ll update this page when it does.

Account
123456789012 · us-east-1
Last checked
8 seconds ago
Reopen setupCheck nowSend to an admin

Show it when your saved deployment's status is pending and lastHeartbeatAt is null.

  • Reload. Read the deployment ID you saved. Reopen setup calls POST /v1/deployments/{id}/installation, which returns the same setup step and token.
  • Second click. Creating the deployment again with the same name and choice returns the same deployment and setup step. A CloudFormation retry keeps its stack name and release, even after you publish a new one.
  • Changed choice. A second call with another cloud or method fails with SETUP_RESERVATION_CONFLICT. Show the first choice's steps, or remove the pending deployment with action: "forget" (see Other delete actions) before the customer picks again.
  • Form closed. The admin can close the AWS tab without creating the stack. Keep this state: nothing exists in their account yet.
  • Stack building. On CloudFormation, setupMetadata.setupReservation.stackStartedAt is set while AWS creates the stack. Show how long it has been building.
  • Stack rolled back. setupMetadata.setupReservation.rolledBackAt is set. Ask the admin to delete the failed stack, where its Events tab shows why, and to start again.
GET /v1/deployments/{id}
{
  "id": "dep_0c29fq4a2yjb7kx3smwdgxlc",
  "status": "pending",
  "setupMethod": "cloudformation",
  "lastHeartbeatAt": null,
  "setupMetadata": {
    "setupReservation": { "stackStartedAt": "2026-01-15T08:52:10Z", "accountId": "123456789012", "region": "us-east-1" }
  }
}

When the setup step reports back, the deployment moves to provisioning. From there your screen shows the same states as every other option: see Show deployment progress.

Test it

  • Each method you offer. Deploy into a test account with each one, and watch your page move from waiting to ready.
  • Cancel the AWS form. Close the CloudFormation tab without creating the stack. Your page must stay on "Finish the setup in AWS".
  • Reload during initial setup. Your page must show the same deployment, and Reopen setup the same stack name.
  • Release, then click. Publish a release and click Deploy at once. Your page shows "Preparing the setup", then succeeds on a retry.
  • A Google Cloud customer. Confirm your page sends them to the portal, or offers Terraform.

Next

On this page