# klickops Handbook > The complete klickops product handbook, in English. Each block below is one page; the canonical HTML lives at the URL under its heading. --- # Your first deploy Section: start URL: https://klickops.io/en/docs/first-deploy Reviewed against: 2026.9.10 From signing up to a running container with a URL, in about five minutes and without touching Kubernetes. ## Before you start You need a container image that listens on a port, or a Git repository klickops can build for you. Nothing else: no cluster, no kubectl, no YAML. If you only want to see it work, `nginx:1.27-alpine` on port 80 is a container that starts in seconds and serves a page. ## Sign up and name your organization Sign in with the email address you signed up with: klickops emails you a 6-digit code, so there is no password to set. GitHub or Microsoft work too. If yours runs on a different address, sign in with it once and confirm the address you signed up with; from then on it signs you in to that account. Add a passkey under **Sign-in methods** on your **Account** page (the **Account** menu, then **Profile**) and you sign in with a touch, no email needed; that is also where your sign-in methods are listed. Signing up drops you on one screen: name your organization, accept the terms, create. The name is prefilled from your account, so the fast path is a single click, and it stays editable if you want your real company on it. That organization is where your plan, your billing and your people live. You will not need to think about it again for a while. ## Create a project A project is a box for related workloads: an app, its database, its domain. Give it a name that describes the thing, not the environment. `shop` rather than `production`, because separate environments are usually separate projects. ## Or start from a blueprint Open **Marketplace** in your organization if the app you want is listed there. Pick a blueprint, review what it will create and provide any requested settings, then install it into a new or existing project. A blueprint is a starting point. After installation, its apps, services, domains and other resources work like anything you created by hand. If your app is not listed, continue with the manual deployment below. ## Deploy the app 1. Open the project and choose **Deploy an app**. 2. Pick a source. Paste a container image reference, or connect a Git repository and let klickops build it. 3. Check the port. klickops inspects the image and fills it in, and the suggestion is usually right. 4. Leave the rest alone. One replica, a TCP health check, CPU and memory sized automatically. These are the defaults because they are what most apps want. 5. Deploy. The app appears in the list and goes green in a few seconds, once the container accepts a connection on its port. ![Two apps running in a project, which is what the list looks like once a deploy finishes.](/handbook/apps-list.webp) ## Put it on the internet Open the app, go to **Domains**, choose **Add domain** and keep **klickops subdomain** selected. You get a hostname that resolves immediately with a certificate already valid, and no DNS to configure. Your own domain is the same dialog under **Your own domain**. Your organization verifies the domain once under **Domains** with one DNS record; then you create the `CNAME` klickops shows you and wait for the check to go green. See [Domains](/docs/domains). ## What to do next - Give it a database and let klickops inject the credentials, rather than pasting them into a secret. See [Databases](/docs/databases). - Read [Organizations, projects, workloads](/docs/concepts) if you want the mental model before you build more. - Everything the UI does, the `klops` CLI and the API do too. Nothing here is a dead end. > [!Careful] > The free plan is one project, one app and one database, with a single replica each and no custom domain. You will hit that quickly if you are building something real, and the limits are enforced when you create, not when you are billed. --- # Organizations, projects, workloads Section: start URL: https://klickops.io/en/docs/concepts Reviewed against: 2026.9.10 The three nouns everything else hangs off, and which one owns what. ## Three levels, and that is all | | What it is | What it owns | | --- | --- | --- | | Organization | Your company on klickops | The plan, the bill, the people, and every project below it | | Project | A box for related workloads | Apps, databases, buckets, scheduled jobs, volumes, and the network rules between them | | Workload | One running thing | Its own domains, configuration, storage and backups | ```diagram-nest Organization: Acme Project: shop App: web Database: orders-db Domain: shop.example.com Project: marketing App: site ``` There is no fourth level. Anything that looks like one is a facet of a workload: a domain belongs to the app it routes to, a volume to the app that mounts it, a certificate to its domain. ## Organizations The organization is the boundary for everything that is not a running container. Access is granted here, the plan applies here, and the invoice is issued here. Someone who accepts an invitation to an organization can see its projects; there is no separate per-project login. You get one when you sign up. Most people never need a second, and a second one is a real separation: separate bill, separate members, no sharing. ## Projects A project is where you group things that belong together. The useful test is whether they talk to each other: an app and the database it queries belong in one project, because inside a project they reach each other by name over a private network with nothing exposed. Two consequences worth knowing up front: - **A workload cannot move between projects.** Not an app, not a database. Splitting one project into two means recreating things, so it is worth a minute of thought at the start. - **Deleting a project deletes what is inside it.** That is the point of the box, and it is the fastest way to clean up an experiment. Project names are unique inside an organization and nowhere else, so two organizations can both have one called `api` without coordinating. > [!Note] > Under the hood a project is a Kubernetes namespace with a random name, and every workload in it is ordinary Kubernetes underneath. That matters if you ever want to leave: **Export** under **Project settings**, **Portability**, gives you the whole project as plain Kubernetes manifests, not a proprietary format. ## Workloads A workload is one running thing you can point at. Today that means an [app](/docs/apps), a [database](/docs/databases), a bucket, a scheduled job, or a volume. Everything you configure hangs off one of them, which is why the handbook has a page per workload rather than a page per setting. If you are looking for where to change something, start from the thing it affects. ## Naming Names are lowercase letters, numbers and hyphens, and they are permanent: renaming a project or a workload is not supported, because the name is the identity other things reference. Name for what a thing is rather than where it runs. `orders-db` survives a move from staging to production; `prod-db-v2` starts lying the moment anything changes. ## Limits The plan sits on the organization and caps how much of each thing you get: projects, apps, databases, replicas, custom domains, and how much CPU, memory and disk a project may reserve in total. Limits are enforced when you create something, not when the bill arrives, so hitting one is an error on the form rather than a surprise later. The current numbers for each plan are on your organization's **Plan** page. --- # Apps Section: workloads URL: https://klickops.io/en/docs/apps Reviewed against: 2026.9.10 A container you deploy, scale, and put on a domain. The starting point for everything else in a project. ## What it is An app is one container image running in your project, with the replicas, resources, storage, and networking that go with it. It is the primary thing klickops deploys, and almost everything else on the platform attaches to one: a domain routes to an app, a volume mounts into an app, a database binds its credentials into an app. klickops starts at the container image. It does not build your code unless you connect a repository, and it does not run a pipeline of its own. If you already have an image, you are two minutes from a URL. ## When you'd use it - You have a container image and want it running with a hostname and TLS. - You have a Git repository and want klickops to build it for you. - You are replacing a docker-compose service or a Heroku dyno with something that survives a node reboot. > [!Not this] > A task that runs on a schedule and exits is a scheduled job, not an app. Apps are expected to stay up, so a container that exits cleanly will be restarted. ## Deploy an app 1. Open your project, go to **Apps**, then **Deploy app**. 2. Choose the source. Either a container image, or a repository klickops builds for you. Paste an image reference and klickops inspects it, then pre-fills the port, the command, and any environment the image declares. 3. Confirm the port your app listens on. The suggestion comes from the image, and it is usually right. 4. Set **Copies**, the number of replicas. One is the default and changes later without a redeploy. CPU and memory are not yours to pick: klickops right-sizes them to what the container uses. 5. Under **Who can reach it?** pick **Anyone (public)** if you want it public now. The default, **Private**, keeps it inside the project; you can add a domain later from the app's Domains tab. 6. Review and deploy. The app appears in the list with a live status while the rollout runs. ![The Apps list of a project, with status and resource use per app.](/handbook/apps-list.webp) ## Operating it day to day Every app opens on **Overview**, and the tab strip is the whole surface. | Tab | What lives there | | --- | --- | | Overview | Status, current image, recent revisions, rollback | | Logs | Live and historical container output | | Metrics | CPU, memory, network traffic, restarts, storage used | | Scaling | Replica count and autoscaling rules | | Domains | Hostnames, TLS, redirects, access mode | | Firewall | Who may reach this app, and what it may reach | | Configuration | Environment variables, secrets, bindings | | Storage | Volumes mounted into the container | | Builds | Appears when the app is built from a repository | | Previews | Per-pull-request environments, when a repo is connected | | Shell | An interactive session inside a running container | | Backups | Restore points covering the app and its volumes | | Advanced | Command, health checks, rollout strategy, security, labels, release command and extra containers | ## Settings reference | Setting | Default | What it does | | --- | --- | --- | | Image | none | The container image and tag. Changing it triggers a rollout, and the confirm dialog shows old and new. | | Port | from image | The port your container listens on. Services and domains route to it. | | Copies | 1 | How many replicas run. Scaling to zero stops the app without deleting it. | | Autoscaling | off | Adds replicas between a floor and a ceiling based on CPU. | | CPU and memory | auto-sized | Right-sized to what the container actually uses. There is no size to pick. | | Health check | on, TCP | Traffic is withheld until the port accepts a connection. A TCP check needs no health endpoint; switch it to HTTP if you have one. | | Security | automatic | Follows what the image declares: an image with a non-root user runs as that user, and an image that runs as root still starts, isolated where the cluster supports it. Override it on the Advanced tab. | | Watch for new tags | off | In the Image dialog: tells you about a newer tag, or rolls it out automatically. | ## Limits and gotchas - **A rollout is not instant.** klickops waits for the new pods to pass their probe before it retires the old ones, so a broken image leaves the previous version serving. Apps with a volume are the exception: the old copy stops first so the new one can attach the disk. - **Scaling to zero keeps the storage.** The app stops costing compute and keeps costing storage. - **The shell is not a deploy target.** Anything you change in a running container is gone at the next rollout. Put it in the image or in configuration. - **Environment changes restart the app.** The save banner says how many pods will roll before you commit. > [!Careful] > Deleting an app keeps its volumes: they stay in the project as detached storage, marked Orphaned on the Volumes page, until you delete them there. ## Related - [Domains](/docs/domains) gives the app a hostname and a certificate. - [Databases](/docs/databases) can inject its credentials straight into the app. - Storage that survives a rollout lives on a volume, mounted from the app's Storage tab. --- # Databases Section: workloads URL: https://klickops.io/en/docs/databases Reviewed against: 2026.9.10 A managed PostgreSQL or Valkey instance that lives inside your project, backed up on a schedule, and reachable from your apps by name. ## What it is A database in klickops is a managed instance you create from the Databases page of a project. klickops runs it, watches it, takes the backups, and patches it once you turn on automatic updates. You get a connection string and a console. Nothing about it lives outside the project, so deleting the project deletes the database with it. Two engines are available today. **PostgreSQL** is the full-featured one, with replicas, connection pooling, extensions, and point-in-time restore. **Valkey** is a single instance for caches, sessions, and queues, either a cache that keeps nothing on disk or a data store persisted to a volume, with no replication and no backups by design. ## When you'd use it - Your app needs a database and you would rather not operate one. - You want connection details injected into the app instead of pasted into a secret by hand. - You are moving off a hosted provider and want the data where your project is. > [!Not this] > A database belongs to one project. Apps in another project cannot reach it, and no firewall rule opens that path, so put the apps that use it in the same project. A database cannot be moved between projects after it is created. ## Create a database 1. Open your project, go to **Databases**, then **Add database**. 2. Pick the engine. Choosing PostgreSQL fills in the defaults below; choosing Valkey switches to the cache defaults and drops the backup step. 3. Set the size. Storage is a slider with your plan's ceiling drawn on it. CPU and memory follow the size you pick and can change later without data loss. 4. Decide how many instances. **PostgreSQL starts at three**, one primary and two replicas, which is what lets it survive losing a node. Drop it to one for a development database, and the cost drops with it. 5. Choose a backup destination. With the default **Production** preset, daily backups are on and the wizard will not finish without a bucket to write them to; **Staging** and **Dev** start without backups. 6. Create. The instance is usually ready in under two minutes, and the page shows the phase while it comes up. ![The Databases list of a project, with the available engines below it.](/handbook/databases-list.webp) ## Connect an app to it Open the database, click **Connect to app**, and choose the app. klickops writes the host, port, database name, user, and password into the app as a binding, so you never paste credentials by hand. The credentials sit on that tab if you need them locally, and reading them needs editor access: they include the live password, so a viewer sees the rest of the database but not its connection details. The password stays masked until you reveal it, and the API never returns it in a list response. ```bash # one query through klickops, no local postgres client needed klops db query orders-db --project shop -- "select count(*) from orders" ``` ## Operating it day to day Every database opens on **Overview**, and the tab strip is the whole surface. Valkey shows a shorter one: it has a command console instead of a SQL editor, and no backups tab. | Tab | What lives there | | --- | --- | | Connect | Credentials, app bindings, connection pooler | | Query | Run SQL against the instance, one statement at a time. Valkey shows a command console | | Browse | Tables and rows: create or drop tables, and edit, add or delete rows in tables with a primary key | | Data & backups | Schedule, restore points, dump import and export | | Storage | Grow the volume. Storage grows and never shrinks | | Updates | Minor patches and major version upgrades | | Metrics | Connections, cache hit rate, disk, CPU | | Logs | Instance logs, live and historical | | Advanced | Tuning: server parameters picked from a workload profile. Extensions, TLS and client access rules are set with `klops databases` or the API | ## Settings reference | Setting | Default | What it does | | --- | --- | --- | | Version | PostgreSQL 16, Valkey 8.1 | Pre-picked so the name is the only field you must fill in. Minor patches install on their own once you turn on automatic updates on the **Updates** tab; a major version change is an explicit upgrade with a plan you approve first. | | Storage | 10 GB for PostgreSQL | Volume size. Grows online, never shrinks. Your plan's ceiling is drawn on the slider. A Valkey cache has no disk; a Valkey data store starts at 1 GB. | | Instances | 3 for PostgreSQL, 1 for Valkey | Three nodes is the default: one primary, two replicas, automatic failover. A single instance costs a third as much and cannot survive a node failure. | | Connection pooler | off | Puts PgBouncer in front of the instance. Turn it on when you have more clients than connections. | | Backups | on, daily at 02:00 UTC, 30 days | PostgreSQL only, on with the **Production** preset, and it needs a destination bucket before it can run. Set up later from **Data & backups**, retention starts at 7 days. Write-ahead logs are kept too, so you can restore to any second inside the window. Valkey has no backups at all. | | Extensions | none | PostgreSQL extensions such as `postgis` or `pgvector`, installed with `klops databases extensions install`. Only the few that preload a library, such as `pg_stat_statements`, restart the instance. | | Parameters | engine defaults | Tuned from a workload profile on the **Advanced** tab. A few, such as `shared_buffers` and `max_connections`, restart the instance, and the button says **Apply & restart** before you commit. | | TLS | on, self-signed | Every instance serves TLS with its own certificate, but clients are not forced to use it. Bring your own certificates with `klops databases tls`. | | Update policy | manual | Whether klickops installs minor patches for you, or only tells you one is available. | ## Limits and gotchas - **Storage only grows.** There is no shrink. Size for a year, not for a decade. - **Valkey has no backups.** A cache keeps nothing on disk, so treat it as disposable. A data store survives restarts on its own volume, but nothing copies it anywhere else. - **PostgreSQL backups need somewhere to go.** Nothing is written until you point them at a bucket. - **Major upgrades are one way.** The plan shows what changes, and the upgrade refuses to start without a successful backup from the last 24 hours. There is no downgrade afterwards. - **The instance is not reachable from the internet.** Apps in the same project reach it by name; nothing outside the project can. > [!Careful] > Deleting a database deletes its volume and its backups. The dialog asks you to type the database name because there is nothing to undo afterwards. Export a dump first if you are unsure. ## Related - [Apps](/docs/apps) is what usually connects to this, from its Configuration tab. - [Domains](/docs/domains) if you are exposing something that talks to it. --- # Buckets Section: workloads URL: https://klickops.io/en/docs/buckets Reviewed against: 2026.9.10 S3-compatible object storage for files an app should not keep on its own disk. ## What it is A bucket is object storage that speaks the S3 API. Your app writes files to it with any S3 client, and the files live outside the container, outside any single machine, and outside the app's lifecycle. Buckets sit alongside databases in a project as a second kind of managed state. Where a [volume](/docs/volumes) is a disk attached to one app, a bucket is a service several things can read and write. ## When you'd use it - User uploads, generated PDFs, exports: anything a browser will download later. - Files more than one app needs to see. - Anything that would make a volume grow without limit. > [!Not this] > Object storage is not a filesystem. There are no real directories, no partial writes, and no appending to a file. Code that expects to seek around a file on disk wants a volume. ## Create one 1. Open the project, go to **Buckets**, then **Add bucket**. 2. Give it a name and a size. 3. Create. It is ready in seconds, since there is no cluster to bring up. ## Connecting an app Open the bucket and go to **Access**: the endpoint, region, bucket name, access key and secret key, everything an S3 client asks for. **Connect to app** writes them into an app as the standard `AWS_*` variables, which most S3 SDKs pick up on their own. The secret key behaves like a secret everywhere else in klickops. Only editors can read it, it is shown when you ask for it, and it is never returned in a list. ## Settings reference | Setting | Default | What it does | | --- | --- | --- | | Name | none | Identifies the bucket. Permanent. | | Size | 10 GB | A hard quota on the bucket. The free plan caps each bucket at 1 GB. | | Versioning | off | Keeps old versions of an overwritten object, so a bad write is recoverable. | | Lifecycle | off | Deletes objects automatically after a number of days. | ## Limits and gotchas - **Versioning is off by default and cannot be applied retroactively.** Turn it on before the overwrite you will regret, not after. - **Lifecycle deletes for real.** An expiry rule is a scheduled deletion, and it does not ask again. - **Listing a huge bucket is slow.** The Objects tab is for looking, not for managing millions of keys. Use an S3 client for bulk work. - **Buckets are not covered by the project's recovery points.** They have their own durability from the storage layer underneath. ## Related - [Volumes](/docs/volumes) for a disk attached to one app. - [Secrets](/docs/secrets) if you prefer to hand the keys over as environment variables. --- # Volumes Section: workloads URL: https://klickops.io/en/docs/volumes Reviewed against: 2026.9.10 Disk that survives a rollout, mounted into an app at a path you choose. ## What it is A container's own filesystem is thrown away every time it restarts. A volume is disk that is not: it keeps its contents across restarts, rollouts and image changes, and appears inside the container at a path you pick. Volumes are created by attaching one to an app rather than on their own. Detaching one, or deleting its app, keeps it and its data in the project as detached storage, marked **Orphaned** on the Volumes page, until you reattach or delete it. ## When you'd use it - User uploads an app writes to disk. - A cache or index that is expensive to rebuild but not worth a database. - Any directory that must still be there after a deploy. > [!Not this] > Two apps cannot write to the same volume. If they need shared state, that is a database or a bucket. Reaching for a shared disk to coordinate two containers is the beginning of a bad afternoon. ## Add one to an app 1. Open the app and go to **Storage**. 2. Choose **Attach volume**: a name, a size, and the path it should appear at inside the container. 3. Save. The app restarts once with the volume attached, and the directory is empty on first mount. Size comes from a slider with your plan's ceiling drawn on it, rather than a free-text field, because a typo in a storage figure is expensive in both directions. ![The Volumes list of a project, with each volume's size and what mounts it.](/handbook/volumes-list.webp) ## Growing one Open the volume and raise the size. It grows in place while the app keeps running, and the new space is available once the filesystem has expanded. There is no shrink. Kubernetes does not offer one, so neither does klickops: getting smaller means creating a second volume, copying, and switching the mount. ## Settings reference | Setting | Default | What it does | | --- | --- | --- | | Name | none | Identifies the volume in the project. Permanent. | | Size | from the slider | How much disk. Grows online, never shrinks. | | Mount path | none | Where the volume appears inside the container, for example `/data`. | | Backup | with the project | Every volume is included in the project's backups once the project's **Backups** page has a schedule. There is nothing to switch on per volume. | ## Limits and gotchas - **Storage only grows.** Size for a year, not for a decade. - **One writer.** A volume is mounted by one app. Scaling that app past one replica needs storage that supports it, or a different shape of state. - **The mount hides what was there.** Mounting at a path the image already populated makes the image's files invisible, not merged. Mount at an empty directory. - **Backups run on the project's schedule.** Until the project's **Backups** page has one, a volume is exactly as durable as the disk under it. > [!Careful] > Deleting an app keeps its volumes as detached storage. Deleting a volume deletes the data with no undo. ## Related - [Apps](/docs/apps) is what mounts a volume. - [Databases](/docs/databases) bring their own storage and need no volume. --- # Scheduled jobs Section: workloads URL: https://klickops.io/en/docs/scheduled-jobs Reviewed against: 2026.9.10 A container that runs on a schedule, does its work, and exits. ## What it is A scheduled job is a container klickops starts on a timetable. It runs, finishes, and goes away until the next time. Each start is a run, with its own exit code and its own logs kept for as long as you ask. This is the counterpart to an app. An app is expected to stay up and gets restarted when it exits; a job is expected to exit, and staying up is the failure. ## When you'd use it - A nightly export, invoice run, or cleanup. - A recurring fetch from somebody else's API. - A migration you want to run on a timer rather than by hand. > [!Not this] > Work that should happen in response to something, rather than at a time, is not a scheduled job. A queue consumer that must always be listening is an app. ## Create one 1. Open the project, go to **Scheduled jobs**, then **New scheduled job**. 2. Give it a name and the image to run. A command is optional; without one the image's own entrypoint runs. 3. Set the schedule. It is a cron expression, and the default `0 * * * *` means the top of every hour. The form spells the expression out in plain language, and the job's **Schedule** tab later lists its next runs, so you can check the expression means what you think. 4. Leave resources on automatic unless you know the job is heavy. 5. Create. The job appears with its next run time and waits. ![The Scheduled jobs list, with the next 24 hours and each job's last result.](/handbook/scheduled-jobs-list.webp) ## Watching it Open the job and the **Runs** tab lists every start with its result and duration. Each run keeps its own logs, which is the difference between "it failed" and knowing why. You can also start a run by hand with **Run now** whenever the job is not paused. That runs the job immediately without touching the schedule, which is how you test the thing before trusting it to a timer. ## Settings reference | Setting | Default | What it does | | --- | --- | --- | | Schedule | `0 * * * *` | Cron expression. Hourly by default. | | Timezone | UTC | Which clock the schedule follows. Set it, for example to `Europe/Zurich`, if your job cares about local midnight. | | Concurrency | Allow | What happens when a run is still going and the next is due. `Forbid` skips it, `Replace` kills the old one. | | Kept runs | 3 successful, 1 failed | How much history and how many logs are retained. | | Resources | automatic | CPU and memory. Raise them for a job that does real work. | | Paused | no | **Pause** stops the schedule without deleting the job; **Resume** starts it again. | ## Limits and gotchas - **`Allow` is the default and it means overlap.** A job that takes longer than its interval will run twice at once. If that is unsafe, set `Forbid`. - **A missed window is not made up.** If the cluster could not start a run, that occurrence is gone rather than queued. - **Logs disappear with their run.** History limits control how far back you can look, and the default keeps only the last three successes. - **A job that never exits blocks the next one** under `Forbid`, and piles up under `Allow`. Give long jobs a timeout of their own. ## Related - [Apps](/docs/apps) for work that must always be running. - [Secrets](/docs/secrets) if the job needs a credential. --- # Domains Section: networking URL: https://klickops.io/en/docs/domains Reviewed against: 2026.9.10 A hostname that routes to your app, with a TLS certificate issued and renewed for you. ## What it is A domain is the public address of an app. You give klickops a hostname and the app it belongs to, and klickops routes traffic to it, issues a certificate, and renews that certificate before it expires. There is nothing to configure in the container. ```diagram-flow Visitor -> DNS -> klickops -> Your app ``` A domain usually serves one app. That is deliberate: a hostname is a facet of the app it points at, so you manage it from the app's Domains tab rather than from a separate list of routing rules. When several apps share one hostname through different paths, you edit it on the project's **Domains** page. ## When you'd use it - Your app should be reachable from a browser. - You want HTTPS without touching a certificate file. - You are moving a hostname from another provider and want the certificate to exist before you cut the DNS over. > [!Not this] > Reaching another workload inside the same project needs no domain. Apps in a project reach each other by name over the internal network, and giving an internal service a public hostname just widens its exposure. ## Get a hostname There are two ways, and the first one needs no DNS at all. **A klickops subdomain.** Choose **Add domain**, keep **klickops subdomain** selected, and klickops assigns one under its own wildcard, resolving immediately with a certificate already valid. On the hosted service the label is a random slug rather than your project's name, so nothing about your tenancy ends up in public DNS. Self-hosted installs use the readable `app.project.your-base-domain` form instead. **Your own domain.** Add the hostname, then point it at klickops. 1. Open the app, go to **Domains**, and add the hostname you own. On the hosted service your organization verifies the domain first, see below. 2. klickops shows the `CNAME` record to create. For a root domain like `acme.ch`, use your provider's ALIAS or ANAME record, or CNAME flattening, with the same target. 3. Create that record with your DNS provider. A wildcard `CNAME` covers every subdomain at once if you plan to add more. 4. Wait for the check to go green. klickops polls DNS and issues the certificate as soon as the record resolves, usually within a minute or two. > [!Note] > Add the domain before you move production traffic. The certificate is issued once DNS resolves, so cutting over after the check goes green means no window where visitors see a warning. ## Verify your domain first On the hosted service an organization proves once that it owns a domain, and every project in it can then use that domain and all of its subdomains. Nobody else can attach a hostname under it, even one you left pointing at klickops. 1. Open **Domains** in your organization's sidebar and choose **Verify a domain**. Owners and admins can; everyone else sees the list. 2. Add the `TXT` record it shows at your DNS provider: the name `_klickops-challenge.` and the value `klickops-verify=…`. 3. Choose **Check again**. Once the record is found the domain shows **Verified**, and you may remove the record again. Verify the domain you registered (`acme.ch`), not each hostname: `shop.acme.ch` and `www.acme.ch` are covered with it. Domains that worked before verification existed keep working. Self-hosted installs skip this step. ## Settings reference | Setting | Default | What it does | | --- | --- | --- | | Hostname | none | The name visitors type. One domain resource per hostname. | | App | required | Which app receives the traffic, and on which port. | | HTTPS | on | Issues and renews a certificate. Leave it on. | | Always use HTTPS | on | Sends plaintext requests to the secure address. | | Require klickops login | off | Only members of the project get through; everyone else is sent to sign in. An internal tool needs no auth code of its own. | | Routes | `/` | Route only a path prefix to an app, when several apps share one hostname. | ## Limits and gotchas - **One hostname, one domain resource.** The controller in front does not merge two definitions of the same host, so a duplicate hostname is rejected rather than silently half-applied. - **Certificates need public DNS.** The issuer proves you control the name over the internet. A hostname that only resolves on your internal network cannot get a public certificate. - **Apex domains need an alias record.** Most DNS providers cannot `CNAME` an apex. Use their alias record type, or point `www` at klickops and redirect the apex to it. - **A domain moves with its DNS.** If another organization later proves control of a domain yours verified, it moves to them and shows as verified elsewhere on your list. Your running domains keep serving, but changing one needs the domain verified again. - **Your own domains depend on your plan.** The free plan serves apps on their klickops subdomain only; paid plans include a set number of custom domains per organization. - **No wildcard certificates on the hosted service.** Every address gets its own certificate. To use one you already have, upload it to the project and pick it as the domain's **Certificate**. - **Removing a domain is immediate.** Traffic stops the moment it is deleted, so move DNS first if the name is live. ## Related - [Apps](/docs/apps) is what a domain points at. - A domain gives the app an address; the project firewall controls who may use it. --- # Firewall Section: networking URL: https://klickops.io/en/docs/firewall Reviewed against: 2026.9.10 Who may reach your workloads, and what they may reach. Deny by default, with three switches that decide the rest. ## What it is Every project starts closed. Nothing outside it can reach in, and what goes out is governed by three switches you own. On top of that baseline you write rules for the traffic you actually want. ```diagram-flow Internet -> Domain -> App -> Database ``` That path works out of the box. Anything not on it needs a rule, which is the point: a workload nobody granted access to is a workload nobody can reach. ## When you'd use it - You want to limit which apps in a project may reach its database. - An app must call an external API and the project is otherwise sealed. - You want to prove, to yourself or an auditor, what a workload can actually talk to. ## The three defaults | Switch | Default | What turning it off means | | --- | --- | --- | | Talk to project apps | on | Workloads inside the project can no longer reach each other. Your app loses its database. | | Resolve names (DNS) | on | Nothing in the project can resolve a name. Almost everything breaks, including anything that depends on a hostname. | | Reach the internet | on | No outbound connections to the public internet. An air-gapped project, which is a real requirement and a loud one. | These are project-wide. Turn one off and it applies to every workload, so treat them as a posture rather than as a knob. > [!Careful] > Switching off **Resolve names (DNS)** breaks more than it looks like it will. Names stop resolving everywhere, and the failures surface as timeouts in unrelated places rather than as a clear denial. ![The Network page: the three baseline switches above the traffic each workload actually produced.](/handbook/network.webp) ## Writing a rule Open **Network** and choose **New rule**. A rule names the workloads it applies to, a peer and ports, and is either Allow or Deny. A domain name can only be allowed; to block one, leave it out of your allow rules. While **Reach the internet** is on, an outbound allow changes nothing. klickops can also **derive** rules from traffic it has actually observed. Rather than guessing what an app talks to, you let it run, then let klickops propose the rules matching the flows it saw. Read them before accepting: observed traffic includes whatever happened, not only what should have. ## Seeing what is happening The **Network** page shows the traffic each workload actually produced, including blocked connections. A blocked one is the useful one: choose **Allow** on it and klickops drafts the missing rule, instead of leaving you to infer it from a timeout. ## Limits and gotchas - **Traffic inside a project is not encrypted by these rules.** The firewall decides who may connect, not what the connection looks like. - **Allow rules only add.** While a default is on, an allow rule next to it changes nothing; to close something, turn the default off or add a Deny rule. - **The internet means the public internet.** **Reach the internet** and public-internet rules never reach private networks or the hosting provider's internal addresses, and a domain-name rule must name a public service. Where your platform administrator turned internet access off, the switch stays off. - **Denials look like hangs.** A blocked connection usually times out rather than being refused, so a mysterious slow request is worth checking on the Network page. - **This needs a network layer (Cilium).** Without it the Network page says so and offers no switches or rules; apps still run, reach each other and reach the internet with the platform defaults. ## Related - [Domains](/docs/domains) is how traffic gets in from the outside at all. - [Apps](/docs/apps) each have their own Firewall tab for rules that concern only them. --- # Deploying from Git Section: delivery URL: https://klickops.io/en/docs/git Reviewed against: 2026.9.10 Connect a repository and let klickops build the image, so a push becomes a deploy. ## What it is klickops starts at the container image. Connecting a repository adds the step before that: klickops clones your code, builds an image, pushes it to its own registry, and deploys it. A push to the branch you chose becomes a new version running. It is not a CI system. There are no pipelines, stages, or build matrices, and there is no place to run your tests. If you need those, keep your CI and let it push an image; klickops will happily deploy that instead. ## When you'd use it - You have a repository and no image, and would rather not maintain a build workflow. - You want a push to `main` to reach production without a manual step. - You want a per-pull-request environment to look at before merging. > [!Not this] > If your build needs secrets, custom toolchains, or a test suite that must pass first, that belongs in your CI. Build there, push the image, and point klickops at it. ## Connect a repository 1. Go to **Repositories** in the organization and connect your provider. GitHub uses an app installation, so klickops never sees a password. 2. Pick the repository and its default branch. 3. klickops inspects the code and proposes how to build it. Accept or override. From then on the repository is available to every project in the organization, so a second app from the same code needs no second connection. ```diagram-flow Push to your branch -> klickops builds -> Image in the registry -> New version running ``` ## How it builds | Builder | When it is used | What it needs | | --- | --- | --- | | Buildpacks | Default. No Dockerfile in the repository. | Nothing. The language is detected and a production image is produced. | | Dockerfile | A Dockerfile is present, or you choose it. | Your Dockerfile. klickops builds it as written. | Builds run in the cluster, isolated, without root. The result is pushed to the registry klickops runs, so your image never leaves the platform unless you configured a registry of your own. ## Watching a build The project's **Builds** page lists every build with its status and full log. A failed build leaves the running version untouched, which is the useful property: a broken commit does not take production with it. ## Settings reference | Setting | Default | What it does | | --- | --- | --- | | Branch | the repository's default | Which branch a push deploys from. | | Subpath | repository root | Build from a subdirectory, for a monorepo. | | Builder | buildpacks | How the image is produced. | | Previews | on | Builds and deploys an environment per pull request, torn down when it closes. Switch it off with **Deploy every pull request** on the repository's page. | ## Limits and gotchas - **A push deploys the branch you picked and nothing else.** Work on other branches builds nothing unless previews are on. - **Every build starts clean.** Builds do not share a layer cache yet, so each one takes about as long as the first. - **A build gets 45 minutes and about 20 GB of disk.** One that runs longer or writes more is stopped and fails, and the running version stays. - **Build logs are per build.** A build that is gone from the list takes its log with it. - **Previews cost what they run.** Each open pull request is a running environment, so a repository with twenty of them is twenty environments. Pull requests from forks get no preview. - **Moved repositories need a new address.** klickops does not follow a host's redirect; after renaming or moving a repository, update its address under **Repositories**. ## Related - [Apps](/docs/apps) is what a build ultimately deploys. - [Secrets](/docs/secrets) for values the running app needs. Build-time secrets are not supported. --- # Backups and recovery Section: operations URL: https://klickops.io/en/docs/backups Reviewed against: 2026.9.10 Scheduled recovery points for a project, and how to get one piece back without disturbing the rest. ## What it is A recovery point is a copy of a project taken at a moment in time: the workloads, their configuration, and the contents of every volume. klickops takes them on a schedule you set and keeps them for as long as you say. Backups are configured per project rather than per workload, because a restore that brings back an app without the volume it writes to is not a restore. ## When you'd use it - Somebody deleted the wrong thing. - A deploy corrupted data and you need yesterday. - You want one app back as it was last night, without touching the rest. > [!Not this] > A backup is not a database backup. PostgreSQL keeps its own write-ahead logs and restores to the second, which is finer than anything here. See [Databases](/docs/databases). This page covers everything around it. ## Turn it on 1. Open the project and go to **Backups**. 2. Pick a cadence: hourly, every six hours, daily, or weekly. Daily runs at 02:00. 3. Set how long to keep them. 4. Save. **Turn on backups** is the one-click version: daily, kept 30 days. The first recovery point appears at the next scheduled time, or immediately if you trigger one by hand. Every volume in the project is included; there is nothing to opt into. klickops picks the method itself: a storage snapshot where the cluster supports it, a file copy otherwise. While backups are on, the project's volume storage is billed once more at the backup rate. ## Restoring A restore is scoped. You choose what comes back: - **The whole project.** Everything in the recovery point. - **One app.** Its resources and the volumes it mounts. - **One volume.** Just that disk, by name. ```diagram-nest Recovery point: shop, 02:00 Restore the whole project App: web Volume: uploads App: api Restore one app App: web Volume: uploads Restore one volume Volume: uploads ``` A restore always puts the chosen items back in place: everything written to them since the recovery point is lost, and there is no undo. Take a fresh backup first if you might want today's data back, and narrow the restore to the app or volume you need so the rest keeps running. Databases are not part of it; they restore from their own page. Progress runs live on the page, step by step, and a restore can be aborted while it works. ## Settings reference | Setting | Default | What it does | | --- | --- | --- | | Cadence | off | hourly, 6h, daily (02:00), or weekly. | | Retention | 30 days | 7, 30, 90 or 365 days, before recovery points age out. | | Method | automatic | A storage snapshot where the cluster supports it, a file copy of every volume otherwise. | | Restore scope | whole project | Narrow to named apps or volumes. | ## Limits and gotchas - **A recovery point that saved no disk data is flagged.** The page warns about points that finished without copying any files and will not restore from them, since that would leave your volumes empty. - **Backups are only as good as the last restore you tried.** Restore one unimportant app once, on purpose, before you need it for real. - **Every restore overwrites what is there.** Items outside the scope keep running, but the ones you restore lose everything written since the recovery point. - **A volume newer than the recovery point is not in it.** A whole-project restore leaves such a volume running, and restoring it on its own, or the app it belongs to, is refused rather than deleting it with nothing to put back. - **One restore at a time.** While a restore runs, another one that touches the same app, the same volume or the whole project is refused until the first finishes or you abort it. - **Retention deletes.** A recovery point past its retention is gone, so retention is a decision about worst-case data loss and not a tidiness setting. - **Backups run at most once an hour.** Through the CLI or the API the cadence can also be a cron schedule, as long as it names a single minute. Retention tops out at 365 days. > [!Careful] > Deleting a project deletes its backup schedule and recovery points with it. If you are cleaning up something that might matter later, restore what you need first, or export it. ## Related - [Volumes](/docs/volumes) are all covered once project backups are on. - [Databases](/docs/databases) keep their own backups on their own schedule. --- # When something is wrong Section: operations URL: https://klickops.io/en/docs/troubleshooting Reviewed against: 2026.9.10 The handful of failures that account for most of them, what each looks like, and what to do. ## Start here klickops tries to say what is wrong in one sentence on the workload itself, so read the status line before anything else. Then the **Logs** tab, which is where the container's own account of its death lives. Three questions answer most of it. Did it ever start? Did it start and then stop? Or is it running and simply not reachable? Those are different problems and the sections below follow that order. ## It never starts **The image cannot be pulled.** The name is wrong, the tag does not exist, or the registry needs credentials klickops does not have. Check the exact reference, and for a private registry check that it has [pull credentials](/docs/registries). A typo in a tag looks identical to a missing image. **It is blocked by the project's capacity.** The status says so directly, with the figures: there is not enough CPU or memory left in the project to start it. Remove or shrink a workload, or move to a bigger plan, which raises every project's capacity. A database refused this way says so on its card. **The plan limit refused it.** This one fails at create time with a message naming the limit, so it is visible immediately rather than as a stall. See [Plans and costs](/docs/billing). ## It starts and then stops **It exits cleanly.** klickops restarts it, because an app is expected to stay up. If the thing genuinely should run once and finish, it is a [scheduled job](/docs/scheduled-jobs), not an app. **It crashes on start.** The logs of the failed attempt hold the reason, and it is usually one of three things: a missing environment variable, a database it cannot reach yet, or a port mismatch. Check that the port you configured is the port the process actually listens on. **It runs out of memory.** The container is killed and restarted, with a restart count that keeps climbing. Memory is auto-sized to actual usage, so a process that genuinely needs more will get more, but a leak will simply be killed repeatedly. > [!Note] > A failed rollout does not take the running version with it. klickops waits for the new copies to pass their health check before retiring the old ones, so a broken image leaves the previous version serving. The app looks unhealthy while it retries, and your users do not notice. ## It runs but is not reachable **Check the health check first.** Traffic is withheld until the port accepts a connection. A process that listens on a different port than the one configured is healthy from your side and invisible from the outside. **Then DNS.** If the hostname does not resolve, the certificate was never issued and the domain check never went green. See [Domains](/docs/domains). **Then the firewall.** A blocked connection times out rather than being refused, so it looks like slowness, not like a denial. The app's **Firewall** tab lists connections blocked in the last 15 minutes, with a one-click allow, which is faster than guessing. See [Firewall](/docs/firewall). ## A database is stuck provisioning Give it two minutes: a PostgreSQL cluster genuinely takes that long to bootstrap. Past that, the usual cause is the project's capacity, described above. If it reports healthy but your app cannot connect, the problem is almost never the database. Check that the app has the binding, and that both are in the same project: an app cannot reach a database in another project, and no firewall rule opens that path. ## When to ask When you have the workload name, the time it started failing, and the exact message. Those three turn a conversation into an answer. ## Related - [Backups and recovery](/docs/backups) when the fix is to go back rather than forward. - [Apps](/docs/apps) for what each tab shows. --- # Alerts and notifications Section: operations URL: https://klickops.io/en/docs/alerts Reviewed against: 2026.9.10 Being told when a workload is in trouble, and choosing where that message lands. ## Two halves **Alerts** decide when something is worth saying, and they are configured per project. **Channels** decide where it is said, and they belong to the organization so several projects can share one. An alert with no channel still shows on the project's **Alerts** page, but nobody is told. A channel carries more than alerts: deploys, restarts, failures and billing events for every project in the organization, filtered by the minimum severity you give it. ## Setting up a channel 1. Go to **Notifications** in the organization. 2. Add a channel: Slack, Microsoft Teams, email, or a generic webhook. 3. Send a test message. Do this now rather than finding out during an incident that the webhook URL was wrong. ## Emails Even without a channel, the owners and admins of an organization are emailed when an app keeps crashing or cannot start, a domain still has no certificate an hour after you added it, or a backup fails, at most once per incident per day. Turn it off for the organization under **Notifications** once a channel receives incidents. Everyone chooses their own mail under **Emails** on their **Account** page: getting-started tips, release notes (at most once a week) and incidents each have a switch. Sign-in codes, invitations, security notices, billing and support replies always arrive. ## Choosing thresholds A fixed set of alerts is always on: crash loops, containers killed for running out of memory, pods not ready for 10 minutes, failed release commands and scheduled jobs, and PostgreSQL trouble. Three fire on sustained resource pressure rather than on a single spike, and have a threshold you set: | Alert | Fires when | | --- | --- | | CPU | Usage stays above the threshold, as a share of the app's CPU limit. Apps on paid plans have no CPU limit, so this only fires where one is set | | Memory | Usage stays above the threshold | | Volume | A volume fills past the threshold | Volume is the one worth setting carefully. CPU and memory pressure degrade a workload; a full disk stops it, and often takes the data with it. > [!Careful] > Thresholds you never act on train you to ignore the channel. If an alert has fired weekly for a month and nobody has done anything, either raise the threshold or fix the workload. A noisy channel is worse than no channel, because it hides the one that matters. ## Settings reference | Setting | Default | What it does | | --- | --- | --- | | Enabled | on | Whether this project alerts at all. | | CPU threshold | 80% of the limit | Sustained CPU above this notifies. | | Memory threshold | 80% of the limit | Sustained memory above this notifies. | | Volume threshold | 85% of capacity | A volume filling past this notifies. | | Channel | every channel in the organization | Firing alerts go to each channel whose minimum severity they meet. Configured per organization. | ## Limits and gotchas - **Alerts are per project.** Every new project starts with them on at the default thresholds, so tune them rather than switching them on. - **Channels are per organization.** Removing one silences every project that used it. - **This is not an uptime monitor.** Alerts describe your workloads from the inside. Whether a visitor can reach your site is a different question, and one an external check answers better. - **Nobody is paged.** A message lands in a channel; it does not escalate and does not wake anybody up. ## Related - [Volumes](/docs/volumes), the resource whose alert matters most. - [When something is wrong](/docs/troubleshooting) for what to do once one fires. --- # Secrets Section: configuration URL: https://klickops.io/en/docs/secrets Reviewed against: 2026.8.7 Values your workloads need and nobody should read back: passwords, API keys, tokens. ## What it is A secret is a value klickops stores encrypted and hands to a workload at runtime, without ever showing it back to you. Plain configuration that nobody would mind reading lives beside it as **Configuration**; the split is about sensitivity, not about format. Secrets are project-level. One set, shared by every workload in the project, because an API key that two apps need is one key rather than two copies drifting apart. ## When you'd use it - A third-party API key, an SMTP password, a signing token. - A credential you rotate, where you want one place to change it. - Anything you would be unhappy to find in a screenshot. > [!Not this] > Database credentials are not this. A database hands its own connection details to an app as a binding, which rotates with the password. Copying them into a secret creates a second copy that goes stale. See [Databases](/docs/databases). ## Add a secret 1. Open the project and go to **Configuration**. 2. Add a key and its value. Keys are uppercase with underscores, the shape environment variables take. 3. Save. Workloads that use the secret restart, and the save banner says how many before you commit. The value is write-only from that moment. You can replace it, and you cannot read it back: not in the UI, not through the API, not in a list response. If you lose it, rotate it at the source. ![The project's Configuration page, showing key names with their values hidden.](/handbook/secrets-list.webp) ## Using one in a workload Open the app, go to **Configuration**, and pick the keys it should receive. Each one arrives as an environment variable under its own name. A workload only gets the keys you select. Sharing the set project-wide is about having one copy, not about handing everything to everyone. ## Settings reference | Setting | Default | What it does | | --- | --- | --- | | Key | none | The environment variable name. Uppercase, underscores, unique in the project. | | Value | none | Write-only. Replaceable, never readable. | | Used by | none | Which workloads receive this key. Changing it restarts them. | ## Limits and gotchas - **You cannot read a value back.** This is the point, and it surprises people once. Keep the source of truth wherever the credential was issued. - **Changing a secret restarts what uses it.** A container reads its environment at start, so there is no way to update one in place. - **A secret is not a file.** For certificates and config files that must exist on disk, mount them as a volume instead. - **Deleting a key breaks whatever expected it.** The workload restarts and the variable is simply gone, which usually surfaces as a crash on boot rather than a clear message. ## Related - [Apps](/docs/apps) receive secrets as environment variables. - [Databases](/docs/databases) supply their own credentials and need no secret. --- # Pull credentials and the build registry Section: configuration URL: https://klickops.io/en/docs/registries Reviewed against: 2026.9.10 Where klickops gets images it cannot reach anonymously, and where images it builds are stored. ## Two different things They sound alike and do opposite jobs. | | What it is | Direction | | --- | --- | --- | | Pull credentials | Login details for a registry your images live in | klickops reads from it | | Build registry | Where klickops puts images it builds for you | klickops writes to it | Most projects need neither. Public images need no credentials, and builds go to the registry klickops runs unless you point them elsewhere. ## Pull credentials Add these when a deployment fails because the image cannot be pulled and the image is private. A public image that fails to pull has a different problem, usually a typo in the tag. 1. Go to **Registries** in the organization. 2. Add the registry host, a username and a token. Use a token or a deploy key rather than a password, because that is what you can revoke without changing your own login. 3. Leave **Project** on **Whole org**, or pick one project to keep the credential there. 4. Deploy again. Apps in the organization, or in the project you picked, can now use images from that host. The token is stored as a secret and never shown back, exactly like every other [secret](/docs/secrets). ## The build registry When klickops builds from a repository, the resulting image has to live somewhere. By default that is the registry klickops runs itself, and nothing leaves the platform. Point it elsewhere when your own systems need to pull those images too, or when policy says artifacts belong in your own registry. You supply the host and credentials that may push, and builds go there instead. ## Settings reference | Setting | Default | What it does | | --- | --- | --- | | Host | none | The registry hostname, for example `ghcr.io` or `registry.example.com`. | | Username | none | The account or robot doing the pull. | | Token | none | Write-only. Replaceable, never readable. | | Build registry | klickops-internal | Where built images are pushed. | ## Limits and gotchas - **Credentials belong to the organization or to one project.** A credential on **Whole org** reaches every project in the organization; one scoped to a project stays there. - **Changing a build registry does not move old images.** Existing deployments keep pulling from where their image actually is. - **Builds check the build registry's certificate.** A push to a registry on plain HTTP or with a self-signed certificate fails until you turn on **Allow plain-HTTP / self-signed certificates** in its settings. - **A rotated token breaks pulls silently until the next one.** Running workloads keep running, because the image is already on the node; the failure appears at the next deploy or restart. - **Some registries need the full path, not just the host.** If a pull keeps failing with correct credentials, check whether the reference includes the project or namespace segment the registry expects. ## Related - [Apps](/docs/apps) is where a private image is used. - [Deploying from Git](/docs/git) is what pushes to the build registry. --- # Members and roles Section: organization URL: https://klickops.io/en/docs/members Reviewed against: 2026.9.10 Who is in your organization and what each of them may do. ## What it is Everyone who can see anything in klickops is a member of an organization with exactly one role. That role decides what they can do across every project in it. There are three, and they are deliberately few. Most access questions are answered by "can this person change things", and inventing a fourth role usually means the projects want splitting instead. | Role | Can | Cannot | | --- | --- | --- | | Member | Work in every project: deploy, edit, restart, restore and delete workloads, and see usage and the plan | Create or delete projects, manage members, or change the plan | | Admin | Everything a member can, plus create and delete projects, invite and remove members, manage API tokens and change the plan | Rename or delete the organization, or manage owners | | Owner | Everything | nothing | ## When you'd use it - Somebody joins and needs access. - A contractor should see logs but not touch production. - Somebody leaves, and their access has to go with them. ## Invite somebody 1. Go to **Members** in the organization and choose **Invite member**. 2. Enter their email address and pick the role. Choose the smallest one that lets them do their job; raising it later is one click. 3. klickops emails them the invitation; you can also copy the sign-in link and send it yourself. They see the invitation the next time they sign in with that address, or right after they create their account. 4. They accept and land in the organization. An invitation is not access. Nobody joins until they accept, and until then they wait under **Pending invitations**, where you can revoke it. The answer you get when inviting is the same for every address, so it never tells you whether somebody already has an account. Admins can also list addresses or a domain (`@acme.io`) under **Allowed to join**. Anyone signing in with a matching verified email is offered an invitation as a member and joins only if they accept. Only your own company's email domain can be listed, never a public provider such as Gmail. ## Accept an invitation Invitations addressed to you show up under **Invitations** in the sidebar, with a count. When you belong to no organization yet, they are on the page you land on after signing in. - **Accept** joins the organization at the role it names and takes you there. - **Decline** removes the invitation. Nobody is told anything beyond that you declined. Signing in never joins you anywhere by itself, and an invitation you leave open does not stop you from creating your own organization. ## Leave an organization Under **Members**, **Your membership** has **Leave organization**. You lose access to the organization's projects right away; nothing in them changes. To come back, an admin has to invite you again. ## Delete your account Your **Account** page ends with a **Danger zone** and **Delete account**. Before anything happens it shows what goes: your sign-in methods and passkeys, your memberships, and any personal API tokens. Tokens you created for an organization belong to it and keep working. Your organizations keep their projects, billing history and support tickets; where those name you, they say "deleted user" instead of your address. You type your email to confirm, every session signs out at once, and one last email confirms it. There is no undo: signing in again with the same address starts a new, empty account. While you are the only owner of an organization, the button is off and the organization is listed. Delete it, or make another member an owner, first. From the terminal: `klops account delete `, with `--dry-run` to see the list without deleting. ## Narrowing access to one project A role applies to the whole organization. A per-project role, set under **Members** in the project's settings, raises or lowers one member's role on a single project, for example viewer on production. It does not hide the other projects: somebody who should only see one belongs in an organization of their own. Use it sparingly. Per-project overrides are how an access model becomes something nobody can reason about; two organizations are often the clearer answer. ## Settings reference | Setting | Default | What it does | | --- | --- | --- | | Role | member | The organization-wide permission level. | | Per-project role | none | `admin`, `editor` or `viewer` on one project, replacing the organization role there. | | Invitation | pending | Grants nothing until accepted. Revocable at any time. | ## Limits and gotchas - **Removing somebody is immediate.** They lose access to this organization's projects at once, their account keeps working, and anything they were running keeps running. - **Only admins create and delete projects.** A member works inside the projects that exist, and deleting one needs admin on that project. - **A per-project role replaces the organization role on that project,** higher or lower, so a member set to viewer on production cannot deploy there. - **Everyone sees what the organization spends.** Usage, Plan and Statements are open to every member; only admins and owners change the plan. - **An SSO group is not an invitation.** On a self-hosted install, when your platform administrator maps a sign-in group to the organization, its members are in automatically, and only a change to that mapping takes them out. You can't leave such a membership from **Members**. > [!Careful] > The only owner cannot be removed, demoted or leave, since nobody could then rename or delete the organization. Make someone else an owner first. ## Related - [Organizations, projects, workloads](/docs/concepts) for what an organization contains. - [Plans and costs](/docs/billing) for what the organization pays, which every member can see. --- # Plans and costs Section: organization URL: https://klickops.io/en/docs/billing Reviewed against: 2026.9.10 What you are charged for, where to see it, and how the plan's limits relate to the bill. ## What it is A plan sits on the organization. It sets the limits, and it includes an amount of credit each month. What you actually run is metered against that credit, and anything beyond it is billed. Usage above the included credit is paid from a prepaid balance. Admins request a top-up or a bigger plan from **Usage** or **Plan**, and it applies once the payment is confirmed; a promo code applies at once. A move to a cheaper plan takes effect on the first of the next month. If credit and balance both run out, the organization gets 48 hours, then its workloads pause until there is credit again. Two numbers matter and they are different questions. **Limits** answer "may I create this", and they are enforced on the form. **Usage** answers "what is this costing", and it accumulates while things run. ## What is metered | Metered | How | | --- | --- | | Compute | CPU and memory actually used by your workloads, over time | | Storage | The size of the volumes and databases you have provisioned | | Object storage | The bytes stored in your buckets | | Protected storage | Every volume in a project, counted again while the project has a backup schedule or recovery points left, because keeping copies costs more than keeping one | | Builds | CPU and memory your builds use while they run | A workload scaled to zero stops costing compute and keeps costing storage. That is the cheapest way to park something you are not ready to delete. ## Where to look - **Usage** shows the current month: credit included, credit used, what is projected, broken down by project and by workload, so a surprise has an address. - **Plan** shows the plan you are on, what it includes, and the plans above it. - **Statements** is the history, kept indefinitely, so last March is still there. ![The Plan page: what the plan includes, what each project uses of it, and the plans above it.](/handbook/billing.webp) ## Limits and the plan Every plan caps the same things: how many projects, apps and databases, how many replicas per app, how many custom domains, and how much CPU, memory and disk a single project may reserve in total. On paid plans an app can still use spare CPU beyond that; memory stays capped. Hitting a limit is a refused create with a message naming the limit, not a silent failure and not a surprise on the invoice. The free plan is deliberately small: one project, one app, one database, one replica each. > [!Careful] > Limits are checked when you create, so a plan downgrade does not switch anything off. What is already running keeps running, and you will not be able to create more until you are back under the limit. ## Settings reference | Setting | Where | What it does | | --- | --- | --- | | Plan | Plan | Sets every limit and the included credit. | | Credit | Usage | Included monthly, consumed by usage before anything is charged. | | Project capacity | Plan | How much CPU, memory and disk each project may reserve. Set by the plan; a bigger plan raises it for every project. | ## Limits and gotchas - **Storage bills whether or not it is used.** A 100 GB volume that holds 2 GB costs 100 GB, because the space is reserved for you. - **Backups are storage too.** While a project has backups, its volume storage is billed a second time at the backup rate, which is why they are off by default. - **Deleting is the only way to stop storage costs.** Scaling to zero stops compute only. - **Every member sees the bill.** Usage, Plan and Statements are open to the whole organization; changing the plan is for admins and owners. ## Related - [Organizations, projects, workloads](/docs/concepts) for what the plan applies to. - [Members and roles](/docs/members) for who may change the plan. --- # Getting help Section: organization URL: https://klickops.io/en/docs/support Reviewed against: 2026.9.10 Ask the klickops team a question from inside the product. We can already see your projects, so you never have to paste a log. ## What it is **Get help** in the top bar opens a support request. It goes to the klickops team, and the whole conversation happens inside the product: you see the reply on your Help page, and you also get it by email. The part worth knowing is what the request carries. klickops already knows your world, so a request records **where you were** rather than a copy of what was on your screen: the organization, the project, the workload you were looking at, and the version you were running. When we open your request we read your actual state at that moment, live. That is why there is nowhere in the form to paste a log, and why a one-sentence description is usually enough. ## When you'd use it - Something is broken and the error message does not tell you what to change. - You are not sure whether a setting does what you think it does. - Production is down and you want a human on it now. > [!Not this] > A missing capability is a feature request, not a support request. Those go on the [roadmap](/roadmap) where other customers can vote for them, and voting is what decides the order things get built in. ## Ask for a question 1. Press **Get help** in the top bar, from whatever page you are on. Starting from the broken thing is better than starting from the dashboard, because the request then points straight at it. 2. Write one line saying what is wrong, then a few sentences of detail: what you expected, what happened instead, and when it started. 3. Pick how urgent it is. **Production is down** jumps the queue ahead of everything else, so keep it for outages. 4. Add a screenshot if there is something to see. Paste it straight from your clipboard, drag it in, or pick a file. Up to three. If you need to go and take one, minimize the composer rather than closing it: what you have written is kept. 5. Send. You land on the request, and you get an email when we reply. ## While it is open **Get help** also lists your requests, and a dot on the button means we have answered one. Each request says whose turn it is: | Status | What it means | | --- | --- | | With support | We have it and owe you a reply. | | We replied | There is an answer waiting for you. | | Resolved | Closed. Replying to it opens it again. | Reply in the thread the same way you wrote the first message, screenshots included. When it is sorted, **Mark resolved** closes it, and either side can reopen it later. When a fix for your request ships, the thread says so: once the version carrying it is live, klickops replies with that version and what changed, and marks the request resolved. If it still happens, reply and it opens again. ## What we can see While a request is open, support can open your project the way you see it: workloads, pods, events, logs, and the recommendations on your project's health. That is what lets us answer without a back-and-forth asking you to paste things. Two things we cannot see. **Your secrets** are never returned by the API, to anyone, including us. And **what is on your screen**, which is exactly why the screenshot is the one attachment the form asks for. > [!Careful] > A support thread is visible to you and to the klickops team, and to nobody else in your organization. A colleague cannot read your requests, so if a request should be handed over, forward the reply rather than sharing the link. ## Limits and gotchas - **A request belongs to one organization.** It picks up whichever one you are working in when you press the button. If you are in the wrong one, switch first. - **Answer in the thread, not by email.** A reply to a notification email reaches a person but does not land in the thread. The link in the mail opens the thread, where everything stays together. - **Screenshots get resized.** Your browser scales them down before upload, which keeps them readable and small. Crop to the interesting part rather than sending a full 4K screen. - **A minimized request survives a reload, its screenshots do not.** The text comes back; images have to be added again. - **There are fair-use limits.** Up to ten open requests at once, twenty messages a day on any one of them, and 20 MB of screenshots a day. Resolving a request frees its place straight away; the daily limits free up 24 hours after the messages or screenshots that used them. A message without screenshots is never held back by the screenshot limit. ## Related - [Plans and costs](/docs/billing) covers billing questions, which are usually faster to answer yourself. - [Members and roles](/docs/members) is where you change who can do what, rather than asking us to. --- # Project and organization settings Section: organization URL: https://klickops.io/en/docs/settings Reviewed against: 2026.9.10 The two settings pages, what each one changes, and the three actions there is no undo for. ## Where each lives Settings sit at the level they affect. A project's settings change that project; an organization's settings change every project below it. Most day-to-day configuration is not here at all: it belongs to the workload it affects. Domains are on the app, backups are on the project's Backups page, and secrets are in Configuration. Settings is for the things that describe the container rather than its contents. ## Project settings Its display name and description, which are cosmetic and safe to change at any time. The project's own name is not editable, because other things reference it. Below them sit **Members**, to give someone a different role in this project than in the organization, **Shared configuration**, and **Portability**, which exports the whole project as plain Kubernetes manifests. Two actions here have consequences: **Transfer to another organization.** The project moves with its workloads, and the plan, billing and members of the destination take over. Useful when a project outgrows the organization it was started in. It only appears when you belong to another organization, and you need to be an admin there too. **Delete the project.** Everything inside goes: apps, databases, volumes, and the recovery points. The dialog asks you to type the project name, because there is nothing behind it. > [!Careful] > Deleting a project deletes its recovery points too, so a backup does not save you from this. If you might want any of it later, restore or export first. ## Organization settings | Section | What it sets | | --- | --- | | Settings | The organization's display name, which only an owner changes, and **Delete organization**, once no projects are left | | Members, Domains, API tokens, Notifications, Registries | Their own rows in the organization's sidebar. See [Members and roles](/docs/members), [Pull credentials](/docs/registries) and [Alerts](/docs/alerts) | How much CPU, memory and disk each project may reserve comes from your plan and is shown on the **Plan** page. There is nothing to set per project; a bigger plan raises it for every project. ## Limits and gotchas - **Names are permanent, display names are not.** Anything a URL, a label or another workload references cannot be renamed. - **A transfer changes who pays.** The destination organization's plan and limits apply immediately, so a project moving into a smaller plan may be over its limits and unable to create anything new. - **Everyone can read these pages, only admins change them.** A member can deploy everything in a project and cannot rename it; only an owner renames the organization. ## Related - [Organizations, projects, workloads](/docs/concepts) for what each level owns. - [Plans and costs](/docs/billing) for the capacity each plan gives a project. - [Members and roles](/docs/members) for who may open these pages. --- # klops CLI Section: tools URL: https://klickops.io/en/docs/cli Reviewed against: 2026.9.10 Everything the web interface does, from a terminal or a script. ## What it is `klops` is the klickops command line. It talks to the same REST API the web interface uses, so almost anything you can click, you can script. A few things are still web-only: filing and answering support requests (`klops support` reads them), live traffic, the activity feed, and your own sign-in methods and email settings. It talks to klickops and to nothing else. No kubeconfig, no direct database connections, no `kubectl` on your machine. That is deliberate: access control, audit and TLS all stay in one place, and your laptop needs no cluster credentials. ## When you'd use it - A deploy from a script, or from your own CI. - Reading logs or running a query without opening a browser. - Doing the same thing across ten projects without twenty clicks. ## Signing in Install it with the one-line command under **Resources**, **CLI & MCP** in klickops, which points it at your server. ```bash klops auth login --server https://klickops.example.com ``` Your browser opens on the sign-in page. Type the code your terminal shows, check the IP address and device the page lists, then approve. If you did not just start the sign-in yourself, deny it. On a machine without a browser, add `--device`: it prints a code you approve from any browser where you are signed in. A sign-in lasts at most seven days, and `klops auth logout` ends it on the server too. That writes `~/.klickops/config.yaml` with permissions `0600`, and refuses to read the file if the permissions are looser. For CI, skip the login and set `KLICKOPS_SERVER` and `KLICKOPS_TOKEN` instead, using an [API token](/docs/tokens) rather than your own account. ## The shape of a command Commands read as noun then verb. ```bash klops apps list --project shop klops logs web --project shop --follow klops db query orders-db --project shop -- "select count(*) from orders" klops apps deploy web --image nginx:1.27-alpine --port 80 --project shop ``` Every read command takes `-o`, which is what makes it scriptable: | Format | For | | --- | --- | | `table` | Reading. The default. | | `json` | Piping into `jq`. | | `yaml` | Diffing, or feeding something else. | | `name` | Just the names, for a shell loop. | | `wide` | Accepted, but today prints the same columns as `table`. | klops prints no colour or escape codes, so `klops apps list -o json | jq` always gets clean output. ## Where settings come from Flag beats environment variable beats config file beats default. So a `--project` on the command line wins over `KLICKOPS_PROJECT`, which wins over the current context in `~/.klickops/config.yaml`. | Variable | Sets | | --- | --- | | `KLICKOPS_SERVER` | Which klickops to talk to | | `KLICKOPS_TOKEN` | The token to authenticate with | | `KLICKOPS_ORG` | Default organization | | `KLICKOPS_PROJECT` | Default project | ## Exit codes Scripts depend on these, so they are stable. | Code | Means | | --- | --- | | `0` | Success | | `1` | Something went wrong | | `2` | You used the command wrong | | `3` | Not signed in, or the token expired | | `4` | Signed in, but not allowed | | `5` | Not found | | `6` | Already exists | | `7` | The request was too large | The useful consequence: `3` and `4` are different questions. A `3` in CI means the token was revoked or mistyped; a `4` means it needs more rights. ## Limits and gotchas - **Anything destructive asks first.** Deleting, revoking, restoring over or rolling back something asks for a yes on a terminal. A script has no one to answer, so the command exits with `2` unless you pass `--yes` (or `-y`). - **There is no cache.** Every command asks the server, because stale state bites worse than a round trip. - **The config file is refused if its permissions are loose.** It holds a token, so a world-readable file is treated as a mistake rather than a preference. - **A token belongs to an organization, not to you.** Scripts should use one rather than your personal session, so a person leaving does not break the pipeline. See [API tokens](/docs/tokens). ## Related - [API tokens](/docs/tokens) for authenticating a script. - [MCP server](/docs/mcp) if the thing driving klickops is an AI assistant rather than a script. --- # MCP server Section: tools URL: https://klickops.io/en/docs/mcp Reviewed against: 2026.9.10 Let an AI assistant operate klickops directly, with your permissions and your audit trail. ## What it is The MCP server is a small program that runs on your machine and exposes klickops to an AI assistant as a set of tools. The assistant can then list your apps, read logs, check why a deployment is unhealthy, and make changes, by calling the same API the web interface calls. It is a client, not a second backend. It holds no state, talks to klickops and to nothing else, and authenticates as you. Everything an assistant does through it is subject to your role and lands in the audit log under your name. ## When you'd use it - You want to ask "why is the checkout app unhealthy" and get an answer that read the actual logs. - You are already working in an assistant and would rather not switch to a browser. - You want a change proposed from real state rather than from a guess. > [!Careful] > An assistant with your permissions can do what you can do, including deleting things. Give it a token scoped to what it needs rather than your own admin session, and prefer a viewer token for anything exploratory. See [API tokens](/docs/tokens). ## Setting it up Under **Resources**, **CLI & MCP**, klickops shows the install command and the exact configuration block for the common assistants. It also lists every tool the server exposes, generated from the same catalog the server itself reads, so what you see there is what the assistant gets. Authentication uses the config file `klops auth login` already wrote, or `KLICKOPS_URL` and `KLICKOPS_TOKEN` if you would rather be explicit. The MCP server reads `KLICKOPS_URL`, not the CLI's `KLICKOPS_SERVER`. ## What the assistant can do Tools are named for what they do: `klickops_list_apps`, `klickops_get_app_logs`, `klickops_restart_app`, `klickops_query_database`. Reading is the bulk of it, and the useful part: an assistant that can see your real state stops guessing. Changes are possible too, and they inherit the permission checks of the endpoints behind them. Every tool tells your editor whether it only reads or deletes and overwrites, so the editor can ask before the risky ones. The MCP server adds no confirmation of its own, though: a token that may delete an app allows an assistant to delete an app. ## Limits and gotchas - **Secrets are never returned.** Tools surface key names, never values, and the endpoints that return a database's or bucket's password are deliberately left out. An assistant cannot read your credentials out of klickops. - **It is local.** There is no hosted variant today, so the server runs where you run it and its network peer is your klickops and nothing else. - **It has no memory.** Every call reads current state. That is the point, and it means an assistant cannot act on something it saw ten minutes ago. - **Scope the token.** The single most useful habit: viewer for questions, editor only when you mean it. ## Related - [API tokens](/docs/tokens) for scoping what an assistant may do. - [klops CLI](/docs/cli) for the same surface driven by a script instead. --- # API tokens Section: tools URL: https://klickops.io/en/docs/tokens Reviewed against: 2026.9.10 Long-lived credentials for scripts, CI and assistants, scoped to a role and optionally to single projects. ## What it is An API token authenticates something that is not a person: a CI pipeline, a deployment script, an AI assistant. It belongs to the organization rather than to you, and it carries a role of its own. That ownership is the point. A token tied to your account stops working the day you leave, taking the pipeline with it. An organization token survives you and can be revoked without touching anybody's login. ## When you'd use it - Deploying from your own CI. - A cron job that runs `klops` somewhere. - Giving an [AI assistant](/docs/mcp) access without handing over your session. ## Create one 1. Open **API tokens** in the organization's sidebar and choose **New token**. Only an owner or admin sees, creates and revokes tokens. 2. Name it after what will use it, not after yourself. `github-actions-shop` tells the next person what breaks if they revoke it; `jan-token` does not. 3. Pick the role. Viewer for anything that only reads. 4. Optionally scope it to specific projects. A token without a project scope reaches the whole organization. 5. Copy the value. It is shown once and never again. ## Using it ```bash export KLICKOPS_SERVER=https://klickops.example.com export KLICKOPS_TOKEN=klp_… klops apps list --project shop -o json ``` Against the API directly it is an ordinary bearer token: ```bash curl -H "Authorization: Bearer klp_…" \ https://klickops.example.com/api/klickops/projects/shop/apps ``` ## Settings reference | Setting | Default | What it does | | --- | --- | --- | | Name | none | What this token is for. Shown in the list, next to who created it; what the token does is logged under the token, not under a person. | | Role | viewer | What it may do: `viewer`, `editor` or `admin`. | | Projects | all | Restricts it to named projects. Empty means the whole organization. | | Last used | never | When it last authenticated, so you can tell a live token from a forgotten one. | ## Limits and gotchas - **The value is shown once.** Lost means rotate, not recover. - **A token does not expire on its own.** It works until revoked, which is why the last-used column matters: it is how you find the ones nobody needs any more. - **Scope by role first, projects second.** Most CI pipelines only need `editor` on one project, and most assistants only need `viewer`. - **A token runs workloads, not the organization.** Even an `admin` token cannot invite or remove members, change the plan or request a top-up, create or revoke tokens, verify domains, redeem promo codes or approve a CLI sign-in: those need a person signed in to the browser. - **It outlives the person who created it.** Leaving the organization or deleting their account does not stop it; that is what keeps your CI running through staff changes. When someone leaves, check the list for the tokens they created and revoke the ones nobody needs. - **Revoking is immediate and unrecoverable.** Anything using it fails on the next call, with exit code `3` from the CLI. > [!Careful] > A token in a repository is a token in everybody's hands. Put it in your CI's secret store, and if one is ever committed, revoke it rather than rewriting history: assume it was read. ## Related - [klops CLI](/docs/cli) reads `KLICKOPS_TOKEN` directly. - [MCP server](/docs/mcp) should get a scoped token rather than your session. - [Members and roles](/docs/members) for the same question about people.