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:
plany registry repositories create apidocker login .plany.cr -u plany -p $(plany registry get-token)docker push .plany.cr//api:v1Build 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 sandboxThe 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 flag | Default | |
|---|---|---|
| Image | --image | required |
| 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=value | none |
| Secrets | --secret VAR=secret | none |
| The port the container listens on | --port | 8080 |
| What the container speaks | --protocol http1 or h2c | http1 |
| vCPUs and memory per instance | --vcpus, --memory | required |
| 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-timeout | 4m |
| A private network to reach | --vpc | none |
--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 → DeployThe 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 → RevisionsA 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 → DeployThe 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 → DeployThe 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:
plany sandbox sandboxes create api --image api:v1 --vcpus 1 --memory 512MiB \ --service-account deployerThat 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 ScalingDeleting 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