Skip to Content
OperateDeployArcade Deploy on your own cluster (Helm)

Arcade Deploy on your own cluster (Helm)

This page is for platform operators who run Arcade with the Helm chart and want developers to ship custom servers into that installation with arcade deploy. Once you turn deployments on, the runs each deployed server as a workload in your own cluster. Bundles go to an object store you own, and runner images come from a registry you choose.

How it works

  • A developer runs arcade deploy against your installation. The publishes the server’s bundle to your artifact store (S3-compatible, GCS, or Azure Blob).
  • The creates the server in the workers namespace (arcade-workers by default). Each deployed server runs as a Kubernetes Deployment of the runner image (arcadedev/runner), one pod by default, and the pod fetches its bundle from the artifact store.
  • Every deployed server gets its own NetworkPolicy. It accepts traffic only from the . Outbound, it reaches the public internet and cluster DNS, but not private address ranges, link-local addresses (including cloud metadata), or the cluster ranges you list, unless you allowlist a range.
  • The deployment budget sizes the workers namespace’s quota, which limits how many deployed servers can run at once.

The ’s service gets a namespaced Role in the workers namespace. That Role covers Deployments, Services, Secrets, ServiceAccounts, NetworkPolicies, ResourceQuotas, Leases, read access to pods and pod logs, and read access to pod metrics. Developers never need cluster access.

Prerequisites

  • A working Arcade installation from the Helm chart, with gateway.hostname, ingress.hostname, or deployments.domain set
  • A bucket in an S3-compatible store, Google Cloud Storage, or Azure Blob Storage for release bundles
  • Credentials for that bucket that the can pick up from its environment. The Arcade Engine reads them from the ambient cloud credential chain, never from the bucket URL and never from a deploy request.
  • Access from your cluster to the runner image, either arcadedev/runner on Docker Hub or a copy in your own registry
  • A CNI plugin that enforces Kubernetes NetworkPolicies, so the per-server isolation takes effect

Enable deployments

Add the deployments values

Deployments are off by default. Turn them on and name your artifact store:

YAML
values.yaml
deployments: enabled: true artifacts: # s3://, gs:// or azblob:// url: s3://arcade-bundles?region=us-east-1

Use an s3://, gs://, or azblob:// URL. The chart refuses to render when deployments.enabled is true and deployments.artifacts.url is empty. The refuses to start when the URL scheme isn’t one it supports, and names the supported schemes.

Upgrade the release

Apply the new values to your existing release:

Terminal
helm upgrade arcade \ oci://public.ecr.aws/s5i6x9d1/charts/arcade \ --namespace arcade \ -f values.yaml

With the defaults, this creates the arcade-workers namespace, a default-deny NetworkPolicy for it, a ResourceQuota and LimitRange, and the ’s Role in that namespace.

Deploy a test server

Ask a developer to deploy a server and check that it reaches Running.

Configuration

All settings live under deployments.* in the chart values. Leave a value at its default unless you need to change it.

Core settings

ValueDefaultDescription
deployments.enabledfalseTurns deployments on.
deployments.providerkubernetesThe deployment provider. kubernetes runs deployed servers in your cluster.
deployments.mutationsAllowedtrueWhen false, the Arcade Engine blocks creating and updating deployments.
deployments.domain""Root domain for deployments, passed to the Arcade Engine. Falls back to gateway.hostname, then ingress.hostname. The chart refuses to render if none of them is set. In the platform’s own cluster, the Arcade Engine reaches deployed servers on their in-cluster Service address.
deployments.timeout5mHow long a deploy may take to become healthy before it’s marked failed.
deployments.autoRolltrueRendered to the Arcade Engine as auto_roll.
deployments.reconciler.enabledtrueRuns the deployment reconciler. It keeps the budget quota in step with deployments.budget.default and removes NetworkPolicies left behind by removed servers.
deployments.budget.default5Deployment budget. The reconciler sizes the arcade-deployments-budget quota from it: budget + 1 pods, so one rollout can surge.
deployments.readinessProberolloutHow the Arcade Engine decides a deployed server is ready.

Artifact store

ValueDefaultDescription
deployments.artifacts.url""Bucket URL for release bundles, for example s3://bundles?region=us-east-1, gs://bundles, or azblob://bundles. Required when deployments are on.

The deployed server’s pod doesn’t hold store credentials. The gives the pod a presigned download URL for its bundle when the store can sign one:

StoreSigns whenOtherwise
S3Any credential. With STS session credentials, the URL expires with the session.The pod reads the store URL directly and needs store credentials.
GCSThe Arcade Engine’s service account holds iam.serviceAccounts.signBlob.Federated credentials can’t sign, so the pod falls back to the store URL.
Azure BlobA shared key is configured (AZURE_STORAGE_KEY).Managed identity can’t mint SAS URLs, so the pod falls back to the store URL.

A signing failure doesn’t fail the deploy.

Runner image and registry

ValueDefaultDescription
deployments.image.repositoryarcadedev/runnerRunner image that deployed servers run in.
deployments.image.tag""Runner image tag. Empty uses the chart’s appVersion.
deployments.registry.server""Registry host to pull the runner image from. When set, it’s prefixed to deployments.image.repository.
deployments.registry.imagePullSecrets[]Names of image pull Secrets for deployed servers’ pods. When dockerRegistry.enabled is true and the chart creates that Secret, the chart adds it to this list.

For a private mirror, push arcadedev/runner to your registry and point the chart at it:

YAML
values.yaml
deployments: registry: server: registry.example.internal imagePullSecrets: - my-registry-pull-secret

Sizing

Every deployed server gets the same size.

ValueDefaultDescription
deployments.sizing.instances1Instances per deployed server.
deployments.sizing.cpuLimit1.0CPU limit per deployed server, in cores.
deployments.sizing.memoryLimit512Memory limit per deployed server, in mebibytes (an integer, not a quantity string).
deployments.runtime.className""RuntimeClass for deployed servers, for example a sandboxed runtime. Empty uses the cluster default.
deployments.runtime.nodeSelector{}Node selector for deployed servers’ pods.
deployments.runtime.tolerations[]Tolerations for deployed servers’ pods.

With the chart, each pod’s requests equal its limits, so pods run in the Guaranteed QoS class. Each pod also has a fixed ephemeral-storage request and limit of 6Gi, which covers a 5Gi volume for the bundle and its virtual environment.

Resource requests and ephemeral storage

Not yet available in a released chart. Newer builds can reserve less than the limit for each deployed server. The chart doesn’t expose these settings yet.

The reads these settings from deployments.kubernetes.instance in its own configuration:

ResourceRequestLimitDefault
CPUcpu_request (cores)cpu_limit (cores)Request equals the limit
Memorymemory_request (mebibytes)memory_limit (mebibytes)Request equals the limit
Ephemeral storageephemeral_storage_request (quantity)ephemeral_storage_limit (quantity)Request equals the limit; limit 6Gi
YAML
engine.yaml
deployments: kubernetes: instance: cpu_request: 0.1 memory_request: 256 ephemeral_storage_request: 512Mi cpu_limit: 1 memory_limit: 512 ephemeral_storage_limit: 6Gi
  • A request below its limit puts the pod in the Burstable QoS class. Under node pressure, the kubelet evicts Burstable pods before Guaranteed pods.
  • The refuses to start when a request exceeds its limit, a request is negative, or an ephemeral-storage value isn’t a Kubernetes quantity.
  • Keep ephemeral_storage_limit at 6Gi or higher. The bundle, temporary, and home volumes add up to 6Gi.
  • The sizes the namespace quota from limits, not requests.

Workers namespace

ValueDefaultDescription
deployments.namespacearcade-workersNamespace deployed servers run in.
deployments.createNamespacetrueCreates the namespace with the restricted Pod Security Standard (enforce, audit, and warn) and a default-deny NetworkPolicy. Helm keeps both on uninstall.
deployments.resourceQuota.enabledtrueRenders a ResourceQuota named <release>-workers-quota from deployments.resourceQuota.hard.
deployments.resourceQuota.hardrequests.cpu: 10, requests.memory: 20Gi, limits.cpu: 20, limits.memory: 40Gi, pods: 50Hard limits for that quota.
deployments.limitRange.enabledtrueRenders a LimitRange for containers in the namespace.
deployments.limitRange.defaultcpu: 1, memory: 512MiDefault container limits.
deployments.limitRange.defaultRequestcpu: 250m, memory: 256MiDefault container requests.

To use a namespace you manage yourself, set deployments.createNamespace: false. The chart then adds no default-deny NetworkPolicy, unless you also set deployments.network.defaultDenyExistingNamespace: true. Only do that when the namespace holds nothing but deployed servers.

The reconciler also maintains its own quota, arcade-deployments-budget. It allows budget + 1 pods (one spare for a rollout), and sizes CPU and memory as that pod count times the per-server limits.

Network

ValueDefaultDescription
deployments.egressAllowlist[]CIDRs deployed servers may reach in addition to the public internet, for example an internal API.
deployments.network.clusterCIDRs[]Your cluster’s pod and service CIDRs. Deployed servers can’t reach them. 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 100.64.0.0/10, and 169.254.0.0/16 stay unreachable as well. Ranges in deployments.egressAllowlist are reachable, and so is DNS (TCP and UDP port 53) to the resolvers below.
deployments.network.dns.modeclustercluster uses cluster DNS. external replaces pod DNS with deployments.network.dns.nameservers and removes every cluster-DNS exception.
deployments.network.dns.nameservers[]Public IPv4 nameservers. Required in external mode.
deployments.network.dns.namespacekube-systemNamespace of the cluster DNS pods (cluster mode only).
deployments.network.dns.podSelector{}Labels of the cluster DNS pods, for example k8s-app: kube-dns (cluster mode only). Empty allows every pod in that namespace.
deployments.network.dns.cidrs[]Extra resolver IP ranges, such as a NodeLocal DNS /32. These allow TCP and UDP port 53 only (cluster mode only).

external DNS mode also needs a bundle endpoint the pod can reach without cluster DNS. Existing deployed servers pick up a DNS mode change only after a redeploy.

Deploy a server

Developers use the same workflow as on Arcade Cloud. They sign in to your installation once, then deploy from the directory:

Terminal
arcade login --url https://arcade.example.com arcade deploy -e src/my_server/server.py

arcade login --url saves a for your installation, and arcade deploy targets the active context. See Arcade Deploy for the full developer guide and the CLI cheat sheet for every flag.

  • Deploy servers built with the Arcade MCP framework for Python. The refuses a bundle without a Python definition or with a missing entrypoint.
  • The refuses a changed redeploy while the previous one is still rolling out.
  • The also refuses a bundle over the installation’s size limit, and discards a virtual environment that the bundle ships.
  • A redeploy that changes the server’s code or secrets replaces the release under the same name and gateway URL. A redeploy of a running release that changes nothing is a no-op. Deploying again after a failed release rolls it out again, even if nothing changed.

Check status and logs

Dashboard and CLI

The Servers page in the dashboard lists deployed servers and their status. A running server whose instance keeps crashing shows as Degraded. A removed server disappears from the list.

From the CLI:

Terminal
arcade server list arcade server get <name> arcade server logs <name> -f

Logs cover the current release. After a crash, they also include the immediately previous instance. Arcade doesn’t keep logs beyond that, so ship pod logs to your own logging stack if you need them longer.

kubectl

Deployed servers are ordinary Kubernetes objects in the workers namespace, so your existing tooling works:

Terminal
kubectl -n arcade-workers get deployments,pods,services kubectl -n arcade-workers get networkpolicies -l app.kubernetes.io/managed-by=arcade-engine kubectl -n arcade-workers get resourcequota

Upgrades

Upgrade the chart with helm upgrade as described in Self-host with Helm. Keep these points in mind:

  • Runner image. With deployments.image.tag empty, the runner tag follows the chart’s appVersion. Pin deployments.image.tag to control which runner tag the configures.
  • Private registry. Mirror the new runner tag into your registry before you upgrade.
  • Namespace. With deployments.createNamespace: true, Helm keeps the workers namespace and its default-deny NetworkPolicy on uninstall, so deployed servers’ objects aren’t deleted with the release.

Troubleshooting

SymptomCause and fix
helm upgrade fails with deployments.artifacts.url must name an S3-compatible artifact store when deployments.enabled is trueSet deployments.artifacts.url.
helm upgrade fails with deployments.domain (or gateway.hostname / ingress.hostname) must be set when deployments.enabled is trueSet deployments.domain, gateway.hostname, or ingress.hostname.
helm upgrade fails with engine.extraConfig sets deployments:The chart owns the deployments: block in the Arcade Engine’s configuration. Use deployments.* chart values instead of engine.extraConfig.
The Arcade Engine doesn’t start and names an unsupported schemeUse an s3://, gs://, or azblob:// URL in deployments.artifacts.url.
A deploy fails because the server never became healthyThe pod started but didn’t answer its health check within deployments.timeout. Read the server’s logs, and check that it binds the port the platform probes. If many servers fail at once, suspect the runner image. If start-up is just slow, raise deployments.timeout.
Deploys stop creating podsA ResourceQuota in the workers namespace is full. Run kubectl -n arcade-workers get resourcequota. The tighter of arcade-deployments-budget and <release>-workers-quota is the one that refuses pods. Raise deployments.budget.default or deployments.resourceQuota.hard, and make sure your nodes can hold the extra servers.
A deployed server can’t reach an internal serviceAdd the service’s CIDR to deployments.egressAllowlist.
NetworkPolicies stay behind after you remove serversWith deployments.reconciler.enabled: false, nothing removes them. Delete NetworkPolicies labeled app.kubernetes.io/managed-by=arcade-engine by hand, or turn the reconciler back on.

Next steps

Last updated on