Stacks
A Stack groups several services — a web app, a worker, a database, a cache — into a single multi-service application you can wire together and deploy as a group. Instead of deploying each service by hand and copying credentials between them, you connect components on a visual canvas and Kuploy injects the right values and deploys everything in the correct order.
Stacks are fully k8s-native and reuse Kuploy's normal deploy engine: every component is a regular service, so logs, domains, backups, monitoring, and billing all keep working exactly as they do for standalone services.
Concepts
| Term | Meaning |
|---|---|
| Stack | A named group of services in an environment. |
| Component | A service (application or database) that belongs to a stack. It's still a normal service. |
| Connection | A typed link from a provider component to a consumer component: a value from the provider (e.g. a database connection string) is injected into the consumer as an environment variable — as soon as you connect, and re-resolved at deploy. |
Creating a stack
- Open your project, then an environment.
- Click Stacks in the environment toolbar.
- Click Create stack, give it a name, and open it.
Adding components
In the stack builder, click Add component and pick an existing service from the environment. The service is tagged into the stack — it keeps all of its own settings. Create the underlying services first (see Deploy Your First App and Databases) if you haven't already.
Component roles
Each application component has a role that decides what kind of workload it deploys as — the stacks equivalent of the different process types in a docker-compose file or Procfile. Select an application node and open Component role in the side panel:
| Role | Runs as | Public? | Use for |
|---|---|---|---|
| Web (default) | Deployment + Service + Ingress | Yes — gets a domain | HTTP apps, APIs, anything users hit |
| Worker | Deployment + Service | No | Queue consumers, background processors, long-running jobs |
| Cron | CronJob on a schedule | No | Scheduled tasks — nightly cleanups, report generation, etc. |
Most components are Web, which is the default — you only change the role for background or scheduled work. Cron requires a cron schedule (e.g. 0 * * * * for hourly). The role only applies to application components; databases ignore it.
A common pattern: point two application components at the same repo but give them different roles and start commands — e.g. a web (Web) and a worker (Worker) sharing one codebase, both connected to the same database.
Connecting components
Connections are the core of Stacks. A connection says "take this field from the provider and inject it into the consumer as an environment variable."
You can create one in two ways:
- Drag from the right edge of a provider node to a consumer node on the canvas, or
- Click Connect and choose the provider, source field, and consumer.
Source fields depend on the provider:
| Provider | Available fields |
|---|---|
| PostgreSQL / MySQL / MariaDB | connectionString, host, port, user, password, database |
| MongoDB | connectionString, host, port, user, password |
| Redis | connectionString, host, port, password |
| Application | host |
For example, connecting a Postgres component's connectionString into your web app as DATABASE_URL injects something like:
postgresql://user:password@my-db-appname:5432/mydb
The host is the database's in-cluster service name, so traffic stays inside your project's private network.
Editing or removing a connection
Select a component on the canvas; its connections appear in the side panel on the right. Each one has two actions:
- ✏️ Edit — opens the connection dialog with the provider/consumer locked, so you can change the source field or env-var name. Saving re-resolves the value and rewrites the consumer's env. This is the right way to change an injected value like
DATABASE_URL— don't hand-edit the managed env block. - 🗑️ Remove — deletes the connection and strips its variable from the consumer's env. (To change the provider or consumer itself, remove the connection and draw a new one.)
The moment you create a connection, Kuploy resolves the value and writes it into a platform-managed block in the consumer's environment variables — you'll see it on the consumer's Environment tab between the # >>> kuploy-stack / # <<< kuploy-stack markers. Removing the connection removes it again.
The block is derived from your connections, so don't hand-edit it — Kuploy recomputes it on connect, disconnect, and deploy, overwriting manual changes (hence the "do not edit"). To change an injected value, change its connection (pick a different source field or env-var name on the Stack page) or the provider database's credentials. Your own environment variables — anything outside that block — are fully editable on the Environment tab as usual.
Deploying
Click Deploy stack. Kuploy:
- Orders components by their connections (providers before consumers).
- Resolves each connection and injects the values.
- Deploys each component using the normal per-service deploy.
Components with no connections between them deploy independently; a cycle (A needs B and B needs A) is rejected.
Deploy stack fails fast if any application component isn't ready to build — before anything is deployed — naming the offending component. Each Git-source component needs:
- a configured source (a selected repository), with its Git provider connected (a provider showing Action Required counts as disconnected), and
- a build registry assigned on the component's Advanced → Build Registry tab (adding a registry under Settings → Registry alone is not enough; see Container Registry).
Fix the named component's General/Advanced settings and deploy again.
Import & export (GitOps)
Open a stack and scroll to the Spec panel to view it as YAML in two formats:
- Native — Kuploy's own format and the source of truth. Round-trippable: you can re-import it.
- Score (lossy) — a Score
score.dev/v1b1export for portability. Score is a vendor-neutral, provisioner-resolved format and Kuploy is the provisioner. It's intentionally lossy — Kuploy-specific settings aren't represented — so it's for interop, not backup.
Use Copy to grab either format for your Git repo.
To import, go to the environment's Stacks page and click Import, then paste a native spec:
name: my-app
components:
- name: web
type: web
serviceType: application
env:
- key: DATABASE_URL
fromComponent: db
field: connectionString
- name: db
type: database
serviceType: postgres
Import wires up the grouping and connections over services that already exist in the environment (matched by name). It does not create services. Create the apps and databases first, then import the spec to group and connect them.
What stays per-component
Because a component is a normal service, everything you already know keeps working per component: deployments and logs, domains and TLS, volumes, backups, monitoring, and billing. Open any component's service page (from the side panel's Open service link) to manage it.