Cover graphic: thirteen operational chores of a customer portal, spread across VPS, Kubernetes, Fargate and Lambda, with the number of people needed per variant.
Summary for AI assistants

There are many ways to run a customer portal (Angular, backend, Postgres). This text compares four of them along an axis from fully self-operated to largely managed: VPS with Docker, Kubernetes, ECS on Fargate, Lambda. Fargate here is a way of buying compute and runs under EKS as well, which is why the variants separate the orchestrator rather than the machine underneath. Other forms sit in between, such as managed app platforms, managed servers and shared hosting, which the text returns to at the end. At this size the running costs of the variants are comparable, the operational simplicity is not. Twelve topics have to be solved in every variant: storage, backup, ingress and routing, certificates, DNS, secrets, networking, access, observability, deployment, scaling, platform upkeep. Sign-in is the thirteenth chore: a self-operated identity service brings a second database with its own version stream and its own security advisories, a managed service such as Cognito takes that away. On the VPS all of them are occupied by defaults, without anybody having decided. On Kubernetes they are twelve deliberate setup steps, each with its own version line. On ECS with Fargate, RDS and S3 most of them disappear. Kubernetes is built for organisations with many services and a platform team, and there it pays off. Two events from 2026 show what doing nothing costs: the retirement of ingress-nginx in March and the end of standard support for EKS 1.33 on 29 July, which raises the control plane from roughly 73 to 438 US dollars a month. For Swiss companies with data residency requirements there are counterparts for most building blocks, but they are hard to find.

It runs on a machine at a hosting provider. Docker on top, a docker-compose.yml, next to it an .env file holding the database password. Deployment is git pull and docker compose up -d over SSH.

This is not a makeshift solution. It is simple, one person understands the whole box, it costs little and it runs for years. To start with, that is a sensible choice, and I would not talk anybody out of it.

The question comes later. What happens when the portal starts to matter? When customers collect their documents there, when downtime gets noticed, when somebody asks where the data sits and who backs it up.

At that point a quote usually lands on the table, and it says Kubernetes. That trades one problem for another.

The yardstick

I am not comparing invoices in this text. At this size all variants sit close enough together that the number convinces nobody. A VPS at fifty francs a month and a comparable setup at a large provider cost about the same.

What differs is operational simplicity. Three questions about it:

How many parts does somebody have to understand? Every component has its own version, its own lifecycle and its own way of breaking.

Who does it? In a company of twelve to fifty people there is rarely a person whose job this is.

What happens if nobody does anything for a year? That is the most honest operational question there is, because it describes the normal state.

The test case

A customer portal of the kind a company with twelve to fifty people needs. Sign-in, uploading and downloading documents, a few lists, notifications by email. Angular in the browser, a backend, Postgres as the database. A handful of concurrent users and a peak on Monday morning.

I have deliberately kept sign-in out of the list, it gets its own section further down.

Four variants, picked along an axis from fully self-operated to largely managed. That is a selection and not a complete list, because managed app platforms, managed servers and shared hosting are in the field too. I come back to those at the end.

0, VPS with Docker. One machine at a hosting provider, everything in containers, docker compose as the tool.

A, Kubernetes. A managed cluster, everything in containers, Postgres included. Angular served from an nginx container. Ingress through an ingress controller.

B, ECS. The AWS container service without Kubernetes, the backend on , RDS for Postgres, Angular on S3 behind CloudFront.

C, Lambda. with a managed runtime, RDS for Postgres, Angular on S3 behind CloudFront.

Three terms need setting apart first.

Kubernetes and EKS. Kubernetes is the platform, an open project. It runs in your own data centre, on rented machines, and at practically every larger provider as a managed offering. Anyone setting it up themselves has the chores from variant 0 and those of Kubernetes at the same time, which is why this text assumes the managed form. EKS is the version AWS manages. The deadlines and prices further down come from there. At a different provider the components are the same and the dates are different.

OpenShift. A Kubernetes distribution from Red Hat, likewise available self-operated or bought as a managed service, including from Swiss providers. It ships a ready answer for several of the topics further down, among them ingress, registry and observability. That takes decisions off your hands and puts a product with its own lifecycle and its own licence costs next to them. For this text it counts as Kubernetes.

Fargate. A way of buying compute, not a platform. It runs under ECS and equally under EKS. The variants here therefore separate the orchestrator, not the machine underneath.

Most companies sit on 0. What they get offered is A. In between lie two variants that rarely come up.

Frontend

Angular builds to static files. HTML, JavaScript, CSS, images, and they do not change between two deployments.

In variants 0 and A those files sit in an nginx container. That gives you a web server to run: a base image with an operating system inside it, nginx itself, and a rebuild plus redeploy on every security advisory.

In variants B and C the same files sit in S3, served by CloudFront. No operating system, no package, no patch.

That is the simplest comparison in the whole text: the same content, once with a server in front of it and once without.

Backend

Here the obvious answer is wrong. Fargate does not take backend patching off your hands.

Layer VPS Kubernetes ECS on Fargate Lambda
Host operating system you you AWS AWS
Container runtime you you AWS not applicable
Base image in the container you you you AWS
Language runtime you you you AWS, with a deadline
Dependencies you you you you
Your code you you you you

The decisive row is the base image. Fargate runs your image, which means your Debian or Alpine, your Node or Java version, your system packages. When an OpenSSL flaw lands there, you rebuild and redeploy, exactly as on Kubernetes or on the VPS. What Fargate removes is the machine underneath.

A base image collects vulnerabilities while sitting on the shelf. An image built six months ago carries known flaws today without anything about it having changed. And what I see in the field: the image gets built once at project handover and never again. There is no pipeline rebuilding it overnight, and nobody reading the scan reports. After two years a container is running with the package state of handover day.

Anyone who wants to do better ends up in a bind. Do not rebuild and the image ages. Rebuild regularly and every run gives you a different artefact than the one you tested, because the package versions move along, so you need tests and a deployment window. Pin the versions to keep the build reproducible and you are sitting on a frozen state again.

The return on all of that is small. A study of container images found a strong relationship between the number of installed packages and the number of known flaws, on average around 1.7 additional ones per package. Updating alone barely moves the number, because the same packages carry flaws in the newest release of the distribution too.

The way out therefore lies in the quantity. Fewer packages in the image, or no image of your own at all. Both are decisions made at build time, and nobody has to repeat them every week afterwards.

With a managed Lambda runtime this does not happen structurally. AWS renews the operating system and the runtime underneath your function without you doing anything.

In exchange Lambda demands a deadline. For Node 20 security updates stopped on 30 April 2026, and from 30 September 2026 existing functions can no longer be updated. Execution is never blocked, functions keep running. You just cannot change them after that without switching the runtime first. So every two to three years a migration comes due that cannot be postponed. That is the honest price of a managed runtime.

Two traps go with it. Anyone packaging their function into a container image because it is convenient is back in the Fargate situation for patching without noticing. And a service on a distroless image has almost no system packages, which shrinks Lambda’s advantage considerably. Both assume somebody who sets that up and maintains it.

Database

This is where the difference in risk is largest.

Postgres in a container means you run it yourself: minor version updates, major version upgrades, backups, restore tests, failover, disk space, monitoring. Docker does none of that for you, and neither does Kubernetes. A StatefulSet is an execution wrapper and not database operations.

With RDS the minor updates run in the maintenance window, backups and point-in-time recovery are built in, failover is a setting. What does change is access: an RDS instance sits in a private network, and I worked through the ways in from outside in Private RDS access in 2026.

The sentence this section is about: a backup that has never been restored is not a backup. A dump job writing every night looks green everywhere. Whether a working database can be built from it, nobody knows, because nobody has tried. That surfaces on the one day it counts.

On the VPS there is an aggravating factor I see often: the backup sits on the same machine. Then it protects against an accidentally deleted record and against nothing else.

On Kubernetes there is a different surprise. A volume lives in exactly one availability zone. The Postgres pod that supposedly heals itself has nowhere to go during a zone outage, because its disk is stuck there. Most people learn this during the incident.

Many teams therefore take the cluster for the application and a managed database next to it. That is sensible, and it removes precisely the most dangerous topics. Everything else in this text stays.

The twelve operational topics

This is the core. Twelve topics that have to be solved in every variant. The difference lies in who solves them and how much of it was a deliberate decision. Lambda has no column of its own, because it differs from ECS in only two rows, and those sit below the table.

Topic VPS with Docker Kubernetes ECS on Fargate, with RDS and S3
Storage bind mounts on the system disk StorageClass, CSI driver, PVCs, expansion RDS manages it
Backup cron with pg_dump, often local dump, WAL archive, storage, restore test, plus the cluster objects built in, point-in-time recovery
Ingress nginx or Traefik by hand ingress controller, move to Gateway API, load balancer controller ALB, one line
Certificates certbot, usually works cert-manager, ACME, DNS challenge ACM, renews itself
DNS by hand at the registrar external-dns or by hand Route 53
Secrets .env next to the compose file Secrets plus an operator for the vault, rotation Secrets Manager with IAM
Networking Docker bridge, host firewall, open ports CNI, IP consumption, NetworkPolicies, CoreDNS security groups
Access SSH keys, and who holds them RBAC, pod identity, kubeconfig IAM roles
Observability docker logs Prometheus with storage, log shipping, alerts CloudWatch
Deployment SSH, git pull, compose up Helm or Kustomize, GitOps, registry CDK deploy
Scaling none, vertical with downtime HPA, metrics-server, Karpenter, eviction rules one target metric
Platform operating system plus Docker engine minor versions, compatibility matrix, node rotation not applicable

Lambda differs from the ECS column in two rows. Scaling disappears entirely, and ingress runs through API Gateway or a function URL rather than a load balancer. The remaining ten rows are the same.

The thirteenth chore: sign-in

A customer portal without sign-in does not exist, and it is still not in the table of twelve. There is a reason for that. Sign-in is an operational chore in its own right, and in quotes it appears as a feature that gets built once.

Three routes, with very different consequences.

Build it yourself. Password hashing, reset by email, session management, lockout after failed attempts, second factor, authorisation checks on every endpoint. Every one of those parts is a known target, and every mistake in them is a data leak. Why I advise against it and what the alternative looks like in three layers is in Never build your own login system.

Run an open identity service yourself. On a VPS or in a cluster this is the obvious route: Keycloak as another container, done. What comes with it is a second complete system with its own database, its own version stream, its own compatibility matrix and its own security advisories. Major version upgrades of identity services are rarely trivial, because the data model and the configuration format move along.

That gives sign-in all twelve topics again in miniature in variants 0 and A: storage, backup, restore test, certificates, upgrades, monitoring. And this one system holds your entire user database.

Take a managed service. On AWS it is called Cognito. The user database, the reset flow, sessions, second factor and lockouts sit with the provider.

Honesty requires this: that is not free either. Setting it up costs days, and the traps are rarely in the reference documentation. I wrote up two of them, the four traps with a custom domain in CDK and the three traps when sending your own mail.

The difference stays clear nonetheless. The traps of a managed service cost you once, at build time. A self-operated identity service costs you again every year.

There is something else you would have to reproduce when running it yourself: a managed service brings new methods along without you doing anything. Cognito ships passkeys, in eu-central-2 as well, which means signing in without a password through a key pair on the device. Anyone running their own identity service would have had to add WebAuthn there themselves.

One thing stays with you in every variant: the check. A session has to be verified before a request reaches the application, otherwise protection hangs on every single endpoint and on the discipline of whoever writes the next one. On AWS this check can be pulled in front of delivery. How that works and where the hard limits sit, 128 MB of memory, five seconds, no private network, is in Lambda@Edge: code at the edge of the world. The code in it is yours, so its upkeep stays with you too.

And back to the guiding question of this text: an unmaintained identity service is the worst possible place for a known flaw. That is where the user accounts sit, and it has to be reachable from the internet, otherwise nobody can sign in.

Configured versus simply there

The most interesting difference is not in the table.

On Kubernetes every one of the twelve rows is a deliberate setup step. Somebody decided, read up, chose and configured. That is a lot of work, and afterwards that person knows what is running there.

On the VPS the twelve rows are occupied too, only nobody decided. They sit there at their defaults, taken from a tutorial, copied from a blog post. That is why the VPS feels so simple: the decisions are invisible rather than absent.

An example from a measurement of my own. I analysed 30 days of firewall logs from a site I run. The same paths had already stood out when I went through the access logs, see Spotting AI crawlers in server logs. What scanners go after looks like this:

/.env                 /backend/.env
/.env.local           /config/.env
/.aws/credentials     /.ssh/id_rsa

On a statically served site this goes nowhere, no such file exists. On a VPS with docker compose it does exist, and the database password is inside it. Whether it is reachable through the web server depends on the nginx configuration, which came from the same tutorial as the rest.

The point is not that the VPS is insecure. The point is that nobody knows the answer to that question, because it was never asked.

What “managed” actually means

This is where the most expensive misunderstanding sits.

A managed Kubernetes service takes over the control plane: API server, etcd, scheduler, controller manager. Their availability and their patches.

Everything that creates work stays with you. The node operating system with kernel and OpenSSL, where the provider publishes new images and you trigger the rollout. The container runtime on every node. The rollout itself, meaning replacing nodes, evicting pods, disruption budgets, maintenance windows. The add-ons such as CoreDNS, kube-proxy and the network plugin, whose versions are tied to the cluster version. Everything you installed yourself. And the minor version upgrade of the cluster, in the right order.

On top of that comes a constraint few people plan for. Kubernetes only allows the kubelet to lag the API server by a limited margin. So you cannot leave the nodes standing when the control plane moves on.

And move on you must. With EKS: 14 months of standard support at 0.10 US dollars per cluster per hour, then 12 months of extended support at 0.60, so roughly 438 instead of 73 a month. After that AWS raises the control plane itself, and the lag constraint drags the node work along, whether the timing suits you or not.

On 29 July 2026 EKS 1.33 left standard support. Anyone who did not upgrade has been paying six times as much since then, without anything about their portal having changed.

How far such an upgrade reaches is shown by the most recent hop: Kubernetes 1.35 deprecated cgroup v1, and the kubelet no longer starts by default on a cgroup v1 node. With the end of support for 1.33, maintenance of certain AWS-managed storage driver components ended as well. A control plane upgrade drags nodes, storage and add-ons behind it.

Running Kubernetes is not patching on demand. It is a calendar somebody else keeps.

The fair objection: there are now operating modes where the provider genuinely manages the nodes, replaces them regularly and patches them. Anyone working that way has largely dealt with the node layer. The container images, the components you installed yourself and the upgrade deadlines remain regardless.

And the second objection: EKS can run on Fargate too. Then there are no nodes at all, so no node operating system, no node rotation and no skew constraint. Three of the thirteen chores fall away with it. What remains is the control plane with its support window and the sixfold bill after fourteen months, the add-ons, the ingress, certificate automation, RBAC, networking, observability and the minor version upgrade. On top come limitations of its own, such as no DaemonSets and fixed combinations of CPU and memory, which means logs and metrics have to be collected differently.

That sharpens the comparison rather than refuting it. With the same compute underneath, ECS costs you four of the thirteen chores and EKS ten. The difference is the orchestrator, not the machine.

The ingress that stopped

In March 2026 the Kubernetes community retired ingress-nginx. No releases, no bug fixes, no security updates. The responsible committees wrote that the component served roughly half of all cloud-native environments, that staying on it exposes you and your users to attack, and that none of the alternatives is a drop-in replacement.

That was five months ago. Somewhere a customer portal is running whose ingress has had no security updates since March, and nobody there knows.

That is exactly what operational simplicity is about. It is not about the half hour of patching a month. It is about the components somebody pulls out from under you, and about who reads the advisory.

Scaling

On Kubernetes, scaling needs three layers: one for the number of pods including a metrics source, one for the number of nodes, plus eviction rules so that scaling down does not drop sessions. The node scaler is a separate project with its own version stream and a compatibility matrix against the cluster version, so another component.

On ECS it is one target metric per service. With Lambda there is nothing to configure at all, at most upper limits so a load spike does not overrun the database. On the VPS there is no scaling, you grow vertically, with downtime. What that means in practice during a load spike is in When the website slows down exactly when it counts.

A point for the other side: a modern node scaler can replace nodes after an expiry time and detect newer images. Switch that on and you are patching your node operating system by replacement. On a well-run cluster the node layer is genuinely covered that way. Configured wrongly, the same automation throws users out of the portal in the middle of an upload.

For our portal, scaling is barely relevant anyway. The difference lies in how much machinery you have to build and maintain in order to have it available at all.

Kubernetes is built for a different size

This matters to me, because otherwise it reads like a settling of scores.

Kubernetes is well built. It came out of environments where thousands of services are run by platform teams, and there the twelve adjustment points are not ballast. They are the purpose. With forty services you set each adjustment point up once and apply it everywhere. Then consistency across all services is worth more than the effort per service, and the arithmetic works.

With one service and one database you set up twelve times and apply twelve times to a single consumer. Same tool, different size, opposite result.

As a rule of thumb: from an in-house operations team upwards, from a double-digit number of services upwards, or when you have to stay portable between providers and data centres. A customer portal for a company of twelve people is none of those cases.

Know-how and headcount

Up to here it has been about technology. The real cost block is something else.

What somebody has to know to run this cluster over three years: the Kubernetes object model, minor version upgrades in the right order including the skew rule, add-on compatibility, ingress and the ongoing migration to the Gateway API, certificate automation, RBAC and network policies, node scaling with eviction, building and hardening container images, reading vulnerability reports, Postgres with backups and restore tests, observability, and debugging at night when a pod will not start.

Fifteen topics. For ECS on Fargate with RDS and S3 about four of them remain: building and regularly refreshing container images, IAM, infrastructure code, and the question of who performs the restore when it matters.

Both require skill. The difference is the scope.

For a company of this size the long list means a hire. The median for DevOps engineers in Switzerland sits at roughly CHF 112’000, senior at about CHF 135’000, and Kubernetes skills add a premium of 10 to 20 percent. With employer costs you land at roughly CHF 170’000 a year.

Two things that tend to get lost:

Part time does not solve it. There is no ten percent Kubernetes person. Anyone doing this four times a year has forgotten how it works by the fifth time, and the environment has moved two minor versions on.

One person is not enough either. Upgrades have deadlines, and deadlines know nothing about holidays or notice periods. Anyone running Kubernetes needs two people who can do it, or a contract with somebody who can.

The VPS has the same problem in miniature. There it is usually the one person who set it up back then, and when they leave, the knowledge leaves with them.

A year in which nobody looks

The wrong question is how many hours of operations a month it takes. The right one is: what happens if nobody does anything for a year?

after twelve months of doing nothing
VPS with Docker Running. Operating system and images carry known flaws, SSH is open, the .env sits on the disk. From the outside everything looks normal.
Kubernetes Cluster outside standard support, control plane six times as expensive, add-ons drifted apart, node images outdated, ingress without patches. The upgrade is now a project.
ECS on Fargate, RDS, S3 Running. Platform and minor database updates have been applied. What is outdated is the base image and the dependencies.
Lambda, RDS, S3 Running. Until a runtime is deprecated, then a deadline with advance notice.

Kubernetes punishes inaction visibly. The VPS punishes it invisibly, which is more dangerous. ECS on Fargate largely forgives it.

And inaction is the normal state in a company of twelve people. That is not negligence. There simply is nobody there whose job it would be.

When Switzerland is a requirement

An objection that comes up in every discussion like this: the large providers are American, and through the CLOUD Act a foreign authority could reach the data.

The swiss hosting label states the requirement precisely. It rests on two promises: the data stays in Switzerland, and third parties can only reach it through Swiss authorities. A large international provider with a Swiss data centre can keep the first promise. Not the second.

That a Swiss region determines where stored data sits, and not yet that nothing leaves the country, is something I took apart in Data in Switzerland: what that actually means technically.

Whether it applies to you is decided by three questions:

Are you subject to professional secrecy or to supervision with written rules? Probability does not count here. For anyone carrying a statutory duty of confidentiality, the theoretical possibility is already the problem. Who Art. 321 of the Swiss Criminal Code actually covers is narrower than many assume: lawyers and auditors yes, not every fiduciary. The distinction including the auxiliary persons rule is in Professional secrecy and the cloud.

Does a customer or a tender demand it? Then the discussion is over, regardless of who is technically right.

What actually sits in the portal? Invoices and appointments are a different matter from client files or health data.

Three times no means the residual risk is manageable and operational simplicity wins. One yes means the balance tips.

We deliberately do not carry the label. We cannot keep the second promise, and a logo whose conditions you do not meet is worse than none.

Anyone who needs it will find a Swiss counterpart for most building blocks:

Building block AWS Swiss counterpart
Serving Angular S3 and CloudFront S3-compatible object storage, several providers
Backend without a cluster ECS on Fargate managed app platform, essentially one provider
Database RDS managed Postgres, essentially one provider
Functions without a process Lambda no counterpart

What is missing has no consequences, because for a portal of this kind I recommend ECS on Fargate anyway. Long sessions, uploads and the occasional report suit a running container better than a function with an execution limit and a cold start, and I measured how large that cold start gets depending on the runtime in Lambda cold start measured. A setup without a cluster is possible with Swiss providers too.

Why the obvious choice rarely fits

And yet almost everybody sits on a VPS or gets offered Kubernetes. That comes down to visibility, and it can be checked.

In August 2026 I ran three searches, using terms a company of this size would actually type. Search results are personal and they change, so this is a snapshot.

“Kundenportal KMU Schweiz” returns nothing but government offerings on the first page: the SME portal run by SECO, EasyGov, cantonal e-services, an association. Not a single commercial offering. The term is taken.

“Webapplikation hosten Schweiz” and “Hosting Kundenportal KMU” return web hosting. Entry tariffs from around ten francs a month, described through disk space, number of domains, number of mailboxes, PHP version and one-click installation for WordPress. The comparison articles list uptime, load time, server location and SSD performance as the selection criteria.

None of those criteria helps. Anyone wanting to run an Angular application with a backend and a Postgres database learns here how many email mailboxes are included in the package.

At the other end sits the provider directory of the swiss hosting label with 29 cloud offerings listed as alternatives to AWS. Eleven of them are virtual machines or servers, eight are Kubernetes or OpenShift. How unreliable the orientation is even there is shown by a probe: filter for Function as a Service and three results come back, and behind them sit a virtual machine, an object store and a Kubernetes service. The categorisation comes from the providers themselves. For context, that directory lists label members and is not a market survey.

Both ends are wrong for a company of twelve people. The web hosting package is built for websites. The cluster demands somebody who knows Kubernetes.

What sits in between and hardly gets found

The right thing exists. It just has no name anybody searches for.

Managed app platforms. From eight francs a month, they turn a Git repository into a running application and take care of builds, certificates, logs, database and backups. They advertise with terms like buildpacks and address people who know a particular American platform. A fiduciary office does not know those terms.

Managed container operations. Among the eight search results exactly one described what this is actually about: Docker and Docker Compose for most workloads, Kubernetes only where genuine horizontal scaling is needed, plus Postgres with point-in-time recovery, a web application firewall, monitoring, backup and a named contact.

That is a fifth variant, and it carries the axis of this text to its end. Built like variant 0, except the twelve topics sit with the provider rather than with you. For a portal of this size it is often the most sensible answer, and it appears in no architecture discussion.

It hardly gets found, because it advertises with the same word as the shared hosting packages next to it. Both say hosting and mean something entirely different.

That is the actual finding. The offering exists at both ends and in the middle. What is missing is a shared vocabulary, and that is why the person searching reliably ends up at one of the two ends.

What serverless does not solve

So that no false impression takes hold. Serverless shifts the risk, it does not remove it.

An open bucket, an overly broad role or a bug in your own application work there exactly as they do in the cluster. Injection and broken authentication work the same everywhere. Your code stays your code, and nobody maintains it for you.

What disappears is the maintenance debt in the substrate. That is a lot, and it is not everything.

Four questions for your provider

When a portal quote is on your table, these questions show the difference:

Which variants did you evaluate before proposing this one? If the answer only knows one variant, it was not a choice.

Who performs the platform upgrade, and how often is it due? With Kubernetes the answer is at least once a year, with a deadline. Anyone who cannot put a number on it has not planned for it.

When was a backup last restored, and how long did it take? There are two answers to that: a number, or an evasion.

What happens if the person who set this up is out for six weeks? That is the question that actually decides the architecture, and it has nothing to do with technology.

I build customer portals so that the answer to the last question is short. That is not an argument against Kubernetes and not one against the VPS. It is an argument for asking the operational question before the technical one.