Skip to content
On this page

CLI

anyport is the terminal client. It talks to the same API the dashboard uses, so the two always agree about what exists.

Install#

curl -fsSL https://anyport.dev/cli.sh | sh

macOS and Linux, on Intel and ARM. The script verifies the download against the release checksums and installs to /usr/local/bin; set ANYPORT_INSTALL_DIR to put it somewhere else. Windows binaries are on the release page.

Add --init to run anyport init as soon as the install finishes, so setting up is a single command:

curl -fsSL https://anyport.dev/cli.sh | sh -s -- --init

It needs a terminal to ask its questions. In a script or a CI job the flag is skipped with a note, and the install still succeeds.

Check it worked:

anyport version

Log in#

anyport auth login

This opens your browser and you sign in the way you normally do. The CLI starts a one-shot listener on 127.0.0.1, and the session token is handed back to that local port, so it never travels anywhere else. It is stored in ~/.anyport/config.yaml with 0600 permissions.

Your own session needs no token to create or rotate. anyport auth logout revokes it, and anyport auth whoami shows who you are signed in as. For CI and scripts, where nobody is there to sign in, use a service token instead.

If you belong to more than one organization, pick one once:

anyport org ls
anyport org use acme

That choice is saved, so no later command needs --org.

Everyday commands#

Almost everything is scoped to a project, passed as --project or -p. The cluster is worked out from the project, the same way the dashboard hides it.

anyport project ls
anyport app ls -p demo
anyport app get api -p demo
anyport logs api -p demo

anyport logs streams from the running pods and colours each pod differently, so a rollout with two replicas is readable. --tail sets how much history to show first, --no-follow prints that history and exits, and --timestamps includes the container runtime's own timestamps.

Deploy an image#

anyport app deploy api -p demo --image ghcr.io/acme/api:v2

This does what the dashboard does: it writes a draft and then publishes it. If someone has an unpublished draft open in the console, the deploy builds on top of that draft rather than discarding their edit, and it says so. --no-publish stops after the draft.

Private images#

A private image needs a registry credential, stored once for the organization and named by the apps that pull with it:

echo "$GHCR_TOKEN" | anyport registry add ghcr --server ghcr.io --username octocat --password-stdin
anyport app create api -p demo --image ghcr.io/acme/api:v1 --registry-secret ghcr
anyport app deploy api -p demo --image ghcr.io/acme/api:v2 --registry-secret ghcr
anyport registry ls

The password or token comes from stdin, or from a prompt that does not echo; it is never a flag, so it stays out of shell history, and nothing prints it back. A credential is offered on every cluster unless --cluster narrows it. Source builds push with one too: the one the app names, else the cluster's own registry service, else the organization's default (anyport registry default ghcr). Rotate an expiring token with anyport registry update ghcr --password-stdin. Admins only, apart from ls.

Deploy from GitHub#

anyport github connect
anyport github repos --owner acme
anyport github branches acme/api
anyport app create api -p demo --repo acme/api --branch main

github connect opens GitHub to install the Anyport app on an account and choose its repositories; GitHub hands you back to the console, which finishes the connection, so be signed in there. github repos lists everything the connected accounts granted, and github connections shows each account and whether GitHub still has it installed.

Secrets#

anyport secret ls -p demo
anyport secret set app-config API_URL=https://api.example.com -p demo
anyport secret set app-config --unset OLD_KEY -p demo

Keys you do not mention are preserved. Values are never printed back, by any command, including --json.

Two refusals worth knowing about. Secrets that are not Opaque are managed from the console form that owns their structure; organization registry credentials have their own command, anyport registry. And a secret whose values came back redacted is refused rather than written, because writing redacted values back would blank every key in it.

Project variables#

anyport project variable ls -p demo
anyport project variable set REGION=eu -p demo
anyport project variable set CLERK_SECRET_KEY=$CLERK_SECRET_KEY --secret -p demo
anyport project variable set STRIPE_KEY --secret -p demo      # lock the value it already has

A variable is what a project's apps reference as ${{ NAME }}. With --secret the value is sealed: written once, never read back, usable only as an app's environment value. ls shows a secret variable's name and (secret), never its value. If a team needs the values readable again, the organization owner can unlock the project from its settings, after which organization admins can reveal them on the Secrets page; anyone who can edit the project can seal them back. Variables you do not mention are left alone. This is the command a blocked anyport plan names when an anyport.yaml declares a secret variable that has no value yet.

Domains#

anyport domain add app.example.com -p demo
anyport domain verify app.example.com -p demo
anyport domain ls -p demo

domain add prints the exact DNS records the server checks for, rather than guessing at them, because the routing target depends on how your cluster is published. Once the records resolve, binding the domain to an app's port is done in the console.

Pull-request previews#

anyport preview ls -p demo
anyport preview get 42 -p demo
anyport logs api -p demo --pr 42

--pr <number> points any project-scoped command at that pull request's preview of the project, so logs, shell, port-forward, app ls and the rest work on a preview the same way they do on the project itself. A preview's own name, as preview ls prints it, works with -p too.

anyport preview offers -p demo                       # open pull requests and their state
anyport preview request 43 -p demo --service db=seeded
anyport preview withdraw 43 -p demo
anyport preview open 42 api -p demo
anyport preview cost -p demo                         # what one preview reserves

preview request takes the same choices as the checkboxes on the pull request's comment: an app is cloned (runs the pull request's code) or shared (the preview reaches the project's own), and a service is fresh, seeded from the project's data, or shared. A pull request from a fork is refused until somebody who can update the project passes --approve, or runs preview approve. Withdrawing deletes the preview's apps and its own databases, and a push does not bring it back; preview request does.

preview settings shows when the project gets previews and what they contain, and any flag changes just that setting:

anyport preview settings -p demo --enabled --creation on-request
anyport preview settings -p demo --expire 12h --max-concurrent 3
anyport preview settings -p demo --service db=seeded --var PAYMENTS_URL=https://sandbox.example

Notifications#

anyport notify destination ls
anyport notify log --destination team-slack

Destinations are set up in the console under Settings → Notifications; the CLI is for the question that comes after, "did it arrive". notify log is the record of every attempt for 30 days, with what the receiver answered, including the ones it refused.

Delete a project#

anyport project delete demo
anyport project delete demo --yes --wait

Deleting a project removes its namespace and everything in it: apps, managed services with their volumes, secrets, routes, tasks, build configs and preview environments. Custom domains stay registered, and backups a service wrote to your own object storage are kept. The prompt lists what the project holds and asks you to type its name; a script passes --yes. A project holding a service with deletion protection is refused until you turn that off with anyport service protect <name> --off.

A delete is never undone, even when the cluster cannot be reached to carry it out yet. --wait follows it until the project is gone. If its cluster is gone for good the delete stays blocked, and --force then removes the project from Anyport without the cluster's confirmation; anything still running there keeps running, unmanaged.

Put back what a cluster lost#

anyport app reapply api -p demo
anyport secret reapply app-config -p demo
anyport domain reapply app.example.com -p demo
anyport project reapply demo

When something is removed from the cluster without being deleted in Anyport, say a namespace deleted with kubectl or a cluster rebuilt under the agent, reapply re-creates it from the configuration Anyport holds. Anything still on its cluster is left alone, so it is safe to run again. project reapply re-creates the project itself; the apps, secrets and domains that went with it are re-applied with their own commands.

Network isolation and shell access#

anyport project get demo                              # shows both
anyport cluster set prod --network-isolation off
anyport org set --shell-access admins

Network isolation keeps each project's apps and databases reachable only from inside the project. It is switched per cluster, for every project on it, and only takes effect where the cluster's network plugin enforces NetworkPolicy; project get says when it does not. Shell access decides who may open a shell, a debug container or a forwarded port in a running container: developers (the default), admins, or off for everyone. It is an organization setting and only an admin can change it. Turning isolation off, or widening shell access, asks first.

Helm charts#

anyport helm search kafka-ui
anyport helm show-values kafka-ui --repo https://provectus.github.io/kafka-ui-charts > kafka-ui.yaml
anyport helm install kafka-ui --chart kafka-ui --repo https://provectus.github.io/kafka-ui-charts -f kafka-ui.yaml -p demo
anyport helm ls -p demo
anyport helm upgrade kafka-ui --version 0.7.6 --set replicaCount=2 -p demo
anyport helm uninstall kafka-ui -p demo

This does not run the helm binary, and you do not need one installed. The release is recorded in Anyport and the cluster's agent installs it into the project's namespace and keeps it reconciled. -f and --set work as they do with Helm, with one difference on upgrade: the values the release already has are kept, so --set changes one key, --unset removes one, -f replaces the values file, and --reset-values starts from the chart's defaults. Without --version the latest chart is installed. A chart from an OCI registry takes its oci:// reference as --chart and needs no --repo.

helm get shows the chart, version, status, pods and services, never the values; helm values prints the values the release was given, in full. If a release is deleted on the cluster directly, helm ls shows it as removed and helm reapply installs it again exactly as recorded.

Releases Anyport manages itself, the ones behind managed services and the cluster's own ingress and certificates, are hidden from helm ls (--all shows them) and refused by upgrade and uninstall. Change those through anyport service.

Backups#

PostgreSQL and MySQL back up to a bucket you own once you give them a backup destination, chosen at install (anyport service install postgresql --set backupDestination=prod-backups) or on the service's Backups card in the console.

anyport service backup ls orders-db -p demo
anyport service backup now orders-db -p demo
anyport service backup restore orders-db -p demo --time 2026-09-05T09:30:00Z

backup ls shows whether backups are configured and healthy, the schedule and retention, the window a restore can reach, and each backup newest first. A failed backup is listed with the operator's own error, which is usually the line that says what to fix.

A restore never overwrites the database it reads from. It creates a new service beside it, named <service>-restored unless you pass --to, so the original is still there to compare against; once the copy looks right, move apps across with anyport service unbind and anyport service bind. Without --time it restores to the latest point the backups reach.

backup rm <service> <backup> removes one backup from the history, and what that means depends on the engine. For PostgreSQL it removes the record of a run: the data stays in the bucket until retention removes it, and what you can restore to does not change. For MySQL it deletes the backup itself. The confirmation says which. backup pause stops the schedule and keeps every backup already taken; backup resume starts it again.

The organization-wide view lives under anyport backup:

anyport backup ls
anyport backup destination ls
anyport backup destination add prod-backups --provider s3 --bucket acme-backups \
  --region eu-west-1 --access-key-id AKIA...
anyport backup archive ls
anyport backup archive restore <id> -p staging

backup ls lists every database that backs up, where to, and how its backups last stood. Adding, changing or removing a destination needs an organization admin. The secret access key is read from ANYPORT_BACKUP_SECRET_ACCESS_KEY, or from stdin with --secret-access-key-stdin; it is never a flag, so it stays out of your shell history, and nothing prints it back. A destination the console cannot reach is saved and marked unverified rather than refused, because a bucket only the cluster can reach still works; backup destination verify checks it again.

When a database with backups is deleted, its backups stay in the bucket as an archive. backup archive restore brings one back as a new database in any project. backup archive rm forgets the archive and leaves its objects in the bucket; with --delete-data it deletes them first, which cannot be undone. Archives are kept until you remove them unless you set anyport backup archive retention <days>.

Check on a cluster#

anyport cluster status prod

One screen that answers "is this cluster OK": whether the agent is connected and how far behind the recommended version it is, each node's CPU, memory, pods and disk against what it can hold, the agent's own pod and how often it has restarted, the build queue, and a list of findings with the worst first. --json adds a verdict of error, warning or ok for a script to gate on.

Node usage and the agent's health are asked of the agent itself. When it is not connected, status says so straight away and shows the nodes it last reported, rather than waiting for an answer that cannot come.

The agent has its own commands:

anyport cluster agent upgrade prod            # to the recommended version, or --version
anyport cluster agent rollback prod           # back to the version before the last upgrade
anyport cluster agent restart prod
anyport cluster agent logs prod --previous    # the container that ran before the last restart
anyport cluster resync prod                   # re-send everything Anyport manages on it

Upgrade, rollback and restart name the versions involved and ask first; pass --yes in a script. Each takes the cluster out of Anyport's reach until the new agent connects, which the command waits for. Workloads keep running throughout. An Anyport Cloud cluster's agent is kept up to date by Anyport.

Persistent volumes that nothing owns any more still hold data and still cost disk:

anyport cluster volume ls prod --orphans
anyport cluster volume rm prod project-old/db-data

Only an abandoned volume can be removed, and Anyport checks again before deleting, so a volume belonging to a paused project is refused however it was listed. The prompt says whether the disk is freed or kept, which depends on the volume's reclaim policy.

Open a terminal#

anyport shell api -p demo

You get an interactive shell in the app's running container, with your local window size and resizing as you resize the terminal. It is the same session machinery as the console's terminal tab, which means the same rules: every session is recorded, sessions count against your organization's limits, and they end on the same idle and duration timeouts.

The shell that runs is chosen by the server, bash falling back to sh. It is not a command you pass in, because the argv is what gets written to the audit record.

Managed services are reachable the same way, which is the quickest route to a psql prompt against your own database:

anyport service ls -p demo
anyport shell postgres-main -p demo --service

Pick a specific replica with --pod, or a sidecar with --container. For a distroless image, or a container that is crash-looping and therefore has no shell to enter, --debug attaches an ephemeral debug container instead:

anyport shell api -p demo --debug

Note

Kubernetes cannot remove an ephemeral container once it is added. It stays in the pod's spec until the pod is replaced.

Point your own tools at a cluster#

anyport shell gets you a prompt inside the cluster. anyport port-forward does the opposite — it brings something in the cluster to your machine, so the tools already on your laptop can reach it:

anyport port-forward postgres-main 5432 --service -p demo

That works against a cluster with no public address and no inbound firewall rule, because the traffic goes over the connection the agent already holds open. There is nothing to expose and no VPN to join.

Take a different local port when the obvious one is occupied, and use --print-url to hand the connection straight to a client:

anyport port-forward postgres-main 15432:5432 --service -p demo
psql "$(anyport port-forward postgres-main 5432 --service --print-url -p demo)"

Apps forward the same way, without --service:

anyport port-forward api 8080 -p demo

The listener binds loopback only and there is no flag to change that: binding every interface would publish a production database to whatever network your laptop is on. A forward is the same grant as a shell — unrestricted access to whatever is listening — so it is recorded, counts against your organization's session limits, and ends on the same idle and duration timeouts.

Export a project as a file#

anyport project export demo -o anyport.yaml

Writes the project's apps, managed services, secret names, tasks, variables and domains as one YAML document, with a published schema an editor can validate against. Secret values are never in it, and neither is the cluster. A plain variable is written in clear, so the command warns when one is named like a credential; --strict fails instead, for a job that commits the file. anyport plan -f shows what applying it would change and anyport apply -f does it; see Config as code.

CI and scripts#

A service token is a credential for an organization that acts as a member with a fixed role. It never signs in, it carries its organization, and it is refused the moment an admin revokes it.

anyport token create github-actions --role developer --expires-in 90

The secret is printed once. Put it in your CI's secret store and export it as ANYPORT_TOKEN; every anyport command then runs as that token, with no login and no --org:

export ANYPORT_TOKEN=apt_…
anyport app deploy api -p demo --image ghcr.io/acme/api:${GITHUB_SHA} --wait

Give CI the least it needs. A developer token can deploy and change resources; a viewer token can only read; an admin token can also manage members and settings, which a deploy job has no reason to hold. Tokens do not count as seats, do not appear in the members list, and show up in the audit log under their own name. anyport token ls shows each token's prefix and last use; anyport token revoke ends one.

The same secret works as a bearer credential against the API directly: Authorization: Bearer apt_….

GitHub Actions#

anyport-labs/deploy-action runs that deploy as a workflow step. Save the token as a repository secret named ANYPORT_TOKEN, build and push the image the way your workflow already does, then:

- uses: anyport-labs/deploy-action@v1
  with:
    token: ${{ secrets.ANYPORT_TOKEN }}
    project: shop
    app: api
    image: ghcr.io/acme/api:${{ github.sha }}

It installs the CLI, checked against the release checksums, and runs anyport app deploy --wait, so the job fails when the rollout does rather than when the image is merely accepted. The CLI stays on the job's PATH for any step after it.

  • wait: false returns as soon as the change is published; wait-timeout (default 5m) bounds the wait.
  • publish: false saves a draft to review and publish from the console instead.
  • cluster picks the cluster when the same project name exists on more than one.
  • cli-version pins the CLI, e.g. v0.0.15; the default is the newest release.

The step's url output is the app's first public address, which is what a GitHub environment wants:

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: ${{ steps.deploy.outputs.url }}
    steps:
      - id: deploy
        uses: anyport-labs/deploy-action@v1
        with:
          token: ${{ secrets.ANYPORT_TOKEN }}
          project: shop
          app: api
          image: ghcr.io/acme/api:${{ github.sha }}

Deploy an immutable tag such as the commit SHA rather than latest, so each deploy is its own revision to roll back to. The action runs on Linux and macOS runners. If the cluster pulls from a private registry, give the app a registry credential once (see Private images); the action only names the image.

People#

anyport member ls
anyport member invite alice@example.com bob@example.com --role developer
anyport member invitations
anyport member cancel bob@example.com
anyport member role alice@example.com viewer
anyport member rm alice@example.com

Each invited address gets an email and joins with the role given (viewer unless --role says otherwise) once it accepts; the address does not need an account yet. Members are named by email everywhere. Service tokens are not people and never appear here; they are managed with anyport token. rm and cancel ask before they act; pass --yes in a script. Admins only, apart from ls and invitations.

Who may see what#

A cluster or project can be protected (members look, do not touch) or restricted (members cannot see it), and grants name the people, or the tokens, who hold a role there anyway. Admins only:

anyport access show prod-eu/shop
anyport access set-level prod-eu protected
anyport access grant prod-eu/shop alice@example.com --role developer
anyport access grant staging github-actions --role developer
anyport access grant prod-eu team:platform --role developer
anyport access revoke prod-eu/shop alice@example.com
anyport access list --member alice@example.com

A team groups people so one grant names them all: anyport team create platform, anyport team add platform alice@example.com, then grant team:platform. anyport team show platform lists its members and, for an admin, what it holds. access revoke, team remove and team delete ask before they act; pass --yes in a script.

A token that is an organization viewer with developer on staging is a least-privilege deploy key. See Access control for how a cluster's level reaches its projects.

Scripting#

--json works on any read command and prints the decoded payload instead of a table. Empty lists come back as [] rather than null, so a jq pipeline does not break on an organization with nothing in it.

anyport app ls -p demo --json | jq -r '.[].metadata.name'

anyport shell exits with the remote command's exit code, so it composes with && and set -e like any other command.

Tab completion comes from anyport completion <shell> for bash, zsh, fish and PowerShell. On top of the command names, project, app, secret, service, domain and organization names, member emails, registry credentials and GitHub repositories and branches complete by querying the API, along with pod and container names for anyport shell. Each lookup gives up after three seconds, so a shell never hangs on TAB. Tab completion comes from anyport completion <shell> for bash, zsh, fish and PowerShell. On top of the command names, project, app, secret, service, Helm release, domain and organization names complete by querying the API, along with pod and container names for anyport shell. Each lookup gives up after three seconds, so a shell never hangs on TAB.

Serve it to an AI agent#

anyport mcp exposes the same read-only view over the Model Context Protocol, so an agent can answer questions about what you have deployed:

claude mcp add anyport -- anyport mcp

It reuses this CLI's session and cannot write anything. See MCP server.

Configuration#

Settings resolve in this order: flag, then environment variable, then ~/.anyport/config.yaml, then the built-in default.

FlagEnvironmentDefault
API--endpointANYPORT_ENDPOINThttps://server.anyport.dev
Console--web-urlANYPORT_WEB_URLhttps://console.anyport.dev
Organization--orgANYPORT_ORGwhatever anyport org use saved
SessionANYPORT_TOKENthe stored login

These are two different hosts. The API endpoint serves the GraphQL API, and the console URL is only used to open the browser at login. Pointing --endpoint at the console returns a web page instead of JSON, and the CLI says so rather than failing obscurely.

Next steps#