PlanyDocs
Sandbox

Deploying

Create a sandbox, deploy new revisions of it, roll back to an earlier one, and give it environment, secrets and a private network.

projects/{project}/locations/{location}/sandboxes/{sandbox}
projects/{project}/locations/{location}/sandboxes/{sandbox}/revisions/{revision}

Before you start

Push your image to a repository in the Container Registry of the project and region the sandbox will run in:

Shell
plany registry repositories create api
docker login .plany.cr -u plany -p $(plany registry get-token)
docker push .plany.cr//api:v1

Build for linux/amd64. An image built only for another architecture is pulled and then fails to start.

Creating a sandbox

A sandbox needs three things: an image, a number of vCPUs and an amount of memory. There is no default size, because no size is right for both a webhook and a game backend.

Sandboxes → New sandbox → give it a name → Image: api:v1 → choose a size → Create sandbox

The URL comes back straight away and never changes. The sandbox is PROVISIONING while its first revision is prepared — tens of seconds for a small image, a few minutes for a large one — and then READY. The CLI and Terraform wait for that unless you pass --wait=false.

A sandbox id is the front of its hostname, so it is lower-case letters, digits and hyphens, starting with a letter, at most 40 characters. The rest of the hostname is derived from the sandbox's identity: deleting a sandbox and creating another with the same id gives the new one a different URL.

Naming the image

In the console and the CLI an image is the repository and a tag or digest — api:v1, api@sha256:… — not the address you docker push to. Through the API and Terraform it is the repository's full name followed by the tag or digest: projects//locations//repositories/api:v1.

The repository has to be in the sandbox's own project and region.

What a revision holds

Everything about what runs is the sandbox's template, and each change to it is a new revision:

CLI flagDefault
Image--imagerequired
Entrypoint, replacing the image's--command (one argument per flag)the image's own
Arguments, replacing the image's--arg (one argument per flag)the image's own
Environment variables, merged over the image's--env KEY=valuenone
Secrets--secret VAR=secretnone
The port the container listens on--port8080
What the container speaks--protocol http1 or h2chttp1
vCPUs and memory per instance--vcpus, --memoryrequired
Requests one instance handles at once--max-concurrency (1–1000)80
Longest a request may take--request-timeout (at most 60m)5m
Longest a new instance may take to accept a connection--startup-timeout4m
A private network to reach--vpcnone

--command and --arg are an argument list, never a shell string: nothing runs your command through sh -c unless you write sh -c yourself.

Use h2c for a gRPC server, or anything else that expects HTTP/2 without TLS on its port. Callers connect to the URL over HTTPS either way.

The request timeout is measured from the moment a request arrives, so it includes any wait for an instance to start. Set --max-concurrency 1 for code that cannot handle two requests at once; Plany then starts more instances instead.

How many instances run, and for how long, is the sandbox's own setting rather than the revision's — see Scaling.

Deploying a new revision

Sandboxes → api → Deploy → change the image, environment, secrets or size → Deploy

The revision serving now keeps serving until the new one is ready, and keeps serving if the new one fails. A bad push costs you the release, not the service. The failed revision's state_reason says why — an image that could not be found, a secret the sandbox could not read.

With the CLI, only what you pass changes and everything else is carried over; --remove-env, --remove-secret, --clear-env and --no-vpc take things away.

Through the API, template is replaced as a whole when it is in the update mask. Send the entire template — read the sandbox, change what you mean to, send it back — or the environment, secrets and size you left out are dropped from the new revision.

Re-deploying a tag

A tag is resolved to a digest when the revision is created, so pushing over v1 afterwards changes nothing that is running. To pick up new bytes under the same tag, deploy again: plany sandbox sandboxes deploy api with no flags resolves the template's tag afresh. In Terraform, deploy by digest — data.plany_registry_image resolves a tag for you — because an unchanged image string is not a change.

Revisions

Every revision is kept until you delete it, with the digest it actually ran:

Sandboxes → api → Revisions

A revision is named after its sandbox and a number — api-00007 — and is PROVISIONING, then BAKING while its image is prepared, then READY or FAILED. Nothing about a revision can be edited.

Delete the ones you will never go back to with plany sandbox revisions delete. The revision serving now and the newest READY one cannot be deleted.

Rolling back

Rolling back is pointing the sandbox at a revision that already exists. It is not a redeploy: the revision is already prepared, so it takes effect in the time an instance takes to start, and it serves exactly the bytes that ran before even if the tag has since moved.

Sandboxes → api → Revisions → Serve this (on the revision to go back to)

The revision has to be READY. A pinned sandbox stays pinned: later deploys create revisions and do not serve them, until you remove the pin with --latest (or serving_revision = ""). A sandbox with no pin serves its newest READY revision.

In Terraform, serving_revision is left alone unless you set it, so a rollback made in the console during an incident is not undone by the next terraform apply.

Secrets

Attach a secret and the container sees it as an environment variable — and, if you ask, as a file:

Sandboxes → api → Deploy → Secrets → add a secret and the variable to expose it as → Deploy

The value never appears in the sandbox's configuration, its revisions or the console: only the secret's name is attached, and the container reads the value when it starts, as the sandbox's own identity. A few rules:

  • The secret must be in the sandbox's project and region. Up to 16 per sandbox.
  • A variable name is upper-case letters, digits and underscores, not starting with a digit, and two secrets cannot claim the same one.
  • A file path is absolute. The file is written readable only by the container's user, and rewritten every time an instance starts.
  • An unqualified secret follows rotations: the next instance to start reads the new value, with nothing to redeploy. Append /versions/{n} to pin one version.
  • If an attached secret cannot be read when an instance starts, the instance does not start: a container never runs with half its credentials.

Reaching a private network

A sandbox is not on any of your networks. To let its code reach something on one — a database, a game server's admin port — connect it:

Sandboxes → api → Deploy → Private network → choose a network → Deploy

The connection is outbound only. The container can open connections to addresses on that network; nothing on the network can open one to the container, whatever the network's firewall rules say; and the internet is still reached the usual way. The network must be in the sandbox's project and region, and choosing it needs vpc.vpcs.use on it. Because it is part of the revision, rolling back also restores the network the earlier revision had. See Private networks.

Running as your own service account

Each sandbox runs as a service account Plany creates for it, allowed to pull its own image and read its own secrets and nothing else, and removed when the sandbox is deleted. To run as a service account of your own instead — say, one that may call other Plany APIs — name it at create:

CLI
plany sandbox sandboxes create api --image api:v1 --vcpus 1 --memory 512MiB \
--service-account deployer

That needs iam.serviceAccounts.actAs on it, and Plany grants your account nothing extra: it can pull the image and read the secrets only if you have given it that access yourself. In Terraform, changing service_account replaces the sandbox, which gives it a new URL.

Changing everything else

Names, labels, who can reach it and how it scales are changed in place and deploy nothing:

Sandboxes → api → ⋯ → Settings, or Scaling

Deleting a sandbox

Its URL stops answering immediately, and its revisions, triggers and instances go with it. A service account you attached is left alone.

Sandboxes → api → ⋯ → Delete sandbox