For Industrial & IoT solutions, go to portainer.industries · For the Kubernetes management platform, go to portainer.io

Technical detail

How each vibe-coded app is isolated, deployed, and governed

This page is written for the person who has to verify the claims: the platform architect, the security reviewer, and the engineer. It covers how a deployed app is confined to a Kubernetes sandbox, the GitOps pipeline every deployment takes, the pod security and network posture, the trust boundaries, and how Portainer-Run installs on a dedicated appliance or your own cluster.

Before you start

What you need

Portainer-Run runs on Portainer Business, and Portainer Business is the operator control plane that reconciles every deployment into your Kubernetes. A single installer stands the whole stack up for you, so the list of things you provide is short.

The storage class, the authenticating ingress controller, the centralized user directory, the network security policies, and the namespaces and quotas are all stood up by the installer as part of the deployment, so they are not things you configure by hand before you begin.

Installation

One installer, two modes

An IT administrator runs a single terminal installer (TUI) on the target host. It fetches and runs a bootstrap script, then walks the rest interactively, standing up the full stack and the Kubernetes components a governed deployment target needs, so there is no manual assembly of ingress, identity, storage, and policy.

curl -sfL https://get.portainer.run/get.sh | sudo sh -

The installer then asks which of the two modes below to use.

Appliance mode, onto a large VM

The installer turns a single virtual machine into a self-contained appliance for hosting vibe-coded apps. It installs KubeSolo, Portainer Business, and Portainer-Run, and deploys the required Kubernetes components: an authenticating ingress controller, a centralized user directory, a storage class, network security policies, and the namespaces and quotas. The result is an isolated environment dedicated to running these apps and nothing else.

BYO mode, onto an existing cluster

The installer targets a Kubernetes cluster you already operate. It installs Portainer Business and Portainer-Run, and deploys the same set of components: an authenticating ingress controller, a centralized user directory, a storage class, network security policies, and the namespaces and quotas. The isolation model is identical to the appliance; the appliance adds a dedicated host underneath it, not a different security posture.

Where the security comes from

The sandbox that confines a deployed app is a property of the Kubernetes components above (namespaces, network security policies, pod security, and the authenticating ingress), not of the appliance. A BYO cluster running these components isolates an app exactly as the appliance does. The appliance is additional assurance through a dedicated host, not the source of the isolation.

Under the hood

How a deployment works

Everything is committed to Git and reconciled by Portainer, so each deployment is governed and fully repeatable.

Runtime detection

Portainer-Run inspects the file structure and matches a runtime in priority order, first match wins. A package.json means Node.js, requirements.txt or .py means Python, .php means PHP, a Gemfile or .rb means Ruby. If everything is static assets it defaults to nginx.

Manifest and init containers

Portainer-Run generates a Kubernetes Deployment manifest for the detected runtime, with three init containers that run in sequence before the app starts:

Once the init containers complete, the main container starts against the pre-populated volume using a stock public runtime image. Git credentials are held in a Kubernetes Secret and injected by reference, so the token never appears in the pod spec.

GitOps commit and reconciliation

Source files are committed to {environment}/{namespace}/{app}/src/ and the manifest to {environment}/{namespace}/{app}.yaml, keeping environments and namespaces cleanly separated in one repo. Portainer-Run then calls the Portainer API to create a GitOps stack pointed at that manifest. Portainer polls the repository on a set interval (five minutes by default) and applies any change.

Updates

An update takes the same path. Drop the revised files, Portainer-Run commits the change, and Portainer reconciles it on the next poll. The PersistentVolume keeps its state across restarts, so uploaded files and anything the app wrote to disk survive the update.

Under the hood

The sandbox: how each app is isolated

The "sandbox" a security team asks for is the isolated environment in which a deployed app can do no harm, and it is assembled from four Kubernetes mechanisms working together rather than from any single control. A deployed app runs with near-zero reach on the network beyond what you explicitly grant, and every part of that is written into the committed manifest or the cluster components, so nothing is applied invisibly. You can read all of it in the manifest committed to your own Git repo and diff it against what lands in your cluster.

Default security context

Each app deploys to the Kubernetes baseline pod security profile. The application container and every init container drop all Linux capabilities, disable privilege escalation, and pin the RuntimeDefault seccomp profile. The pod does not mount a Kubernetes service account token, so a deployed app carries no ambient credentials to the cluster API. The generated Deployment sets these fields directly:

spec:
  template:
    spec:
      automountServiceAccountToken: false
      containers:
        - name: app
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop: ["ALL"]
            seccompProfile:
              type: RuntimeDefault
          resources:
            requests:
              cpu: "100m"
              memory: "1Gi"
            limits:
              cpu: "1"
              memory: "4Gi"

Resource requests and limits are applied to every app so nothing runs unbounded, and because they live in the committed manifest a platform administrator can raise them in Git for a workload that genuinely needs more.

What is deliberately not forced, and why

We do not force runAsNonRoot or a read-only root filesystem. The sync, install, and env-writing init steps legitimately write to the mounted volume, and many AI-generated images start as root and expect a writable filesystem. Forcing either would break the common case rather than harden it. The php-apache runtime is the one place capabilities are not fully dropped: it drops all, then adds back only CHOWN, SETUID, SETGID, and NET_BIND_SERVICE, which the official Apache image needs to boot. Static sites avoid this entirely by running the unprivileged nginx image as UID 101 on port 8080.

Network access with Pomerium

A hardened pod still needs a front door you control. We recommend and fully support Pomerium as the in-cluster ingress for Run apps. Configured in front of each deployed app, Pomerium pre-authenticates every request against the identity provider you already run, so an app an AI builder deploys is never openly reachable on the network by default and is served only to authenticated internal users. This turns "who can reach this app" from an afterthought into a policy set at the ingress, consistent across every app Run deploys.

Under the hood

Trust boundaries

Each boundary crossing is authenticated, and each zone has a distinct owner. There is no push path from the person deploying to the cluster; the only way in is through the sanctioned repository.

Where credentials live

The git token for a source repository is stored encrypted and injected into the clone step by Kubernetes Secret reference, so it never appears in a deployment spec or on a command line. Detected application secrets travel the same way, by reference, and never reach the repository. Portainer-Run has no external address of its own and creates no new login surface; authentication and RBAC are Portainer's, and add-on access is separately grantable per user and team.

The MCP endpoint

Portainer-Run exposes a Model Context Protocol endpoint so an AI coding tool can deploy with no UI in the path. It is reached through the same authenticated Portainer gateway, authorized the same way, and runs the identical governed pipeline underneath: the same git commit, the same generated manifest, the same secrets handling, and the same RBAC scope. MCP clients authenticate with a Portainer API access token; git targets cannot be created over MCP, which remains a deliberate UI action.

Claims this architecture supports

People deploying never receive cluster credentials, git credentials, or a kubeconfig at any point. Every deployment is committed to a sanctioned repository before it runs, and the running state always tracks that repository. The application inventory is complete by construction, because the governed pipeline is the only way in and every app carries the managed-by=portainer-run label with the deploying identity recorded. Removal is as clean as deployment: one commit removes the app's manifest and source, and the workload leaves the cluster on the next reconcile.

Reference

Supported runtimes

Detection is automatic and runs in priority order. If nothing matches, Portainer-Run falls back to nginx.

package.json

Node.js 22

Runs on node:22. Start command from the start script, else node server.js / index.js / npm start.

Default port 3000
requirements.txt / .py

Python 3.13

Runs on python:3.13-slim. Targets main.py, app.py, server.py, or run.py, else python app.py.

Default port 8000
.php

PHP 8.4

Served with Apache on php:8.4-apache.

Default port 80
Gemfile / .rb

Ruby 3.4

Runs on ruby:3.4-slim. Rack apps use bundle exec rackup, else app.rb or server.rb.

Default port 9292
static assets

Static (nginx)

HTML, CSS, JS, images, and so on, on nginxinc/nginx-unprivileged:alpine running as UID 101. A single non-index.html file is renamed on commit.

Default port 8080

Know before you go

Limitations

Go deeper

Full documentation

The documentation has the complete reference: the deployment form, the application catalogue and template format, the Assistant, the aggregated status architecture, and configuration.