Cloud Infrastructure Without Terraform and Ansible

The question of the Infrastructure-as-Code approach comes up in every other technical discussion, and almost always as an either/or choice: Terraform or Ansible? At uCloud, the answer to both is no. This isn’t a matter of tool preference. The two tasks for which Terraform and Ansible are typically used are handled by other components in a cloud-native platform.

2026-09-29 | Marion | Read: 5 min | Tech Stack | Subscribe via RSS

Deploying Cloud Infrastructure and Applications Declaratively Without Terraform and Ansible

Terraform and Ansible Were Never Competitors

Comparing these two tools is like comparing tools designed for two different tasks. For years, they have therefore been used together rather than as alternatives.

  • Terraform creates infrastructure: VMs, networks, load balancers, and, if desired, Kubernetes clusters. The end result is an infrastructure on which additional components can be deployed
  • Ansible configures this infrastructure and deploys applications on it. Packages are installed, configuration files are written, and services are started.

One builds the house; the other furnishes it.

The interesting question, therefore, is not which of the two tools wins, but which components take on these two tasks in a cloud-native approach.

Two tasks, two cloud-native tools:
At UhuruTec, both tasks are also covered, though with different tools.

Task traditional Approach Cloud-Native at UhuruTec
Build Infrastructure (VMs, Load Balancer, Kubernetes) Terraform Cluster API
Configure and Deploy Applications Ansible GitOps-Operator (Argo CD)

How does this work in practice?

It all starts with a bootstrap. This cannot be completely avoided: the first layer—on which everything else is built—has to be created somewhere. Once this step is complete, the way we work changes fundamentally.

Anyone who needs infrastructure defines, for example, a Kubernetes cluster. Kubernetes-as-a-Service is provisioned declaratively via the Cluster API. As a result, it takes over the task that would otherwise be handled by a Terraform configuration. The difference lies in where the provisioning takes place: it is defined and managed as a Kubernetes resource within the cluster, rather than by an external tool with its own state. From this point on, the GitOps operator takes over. It reads the desired state from a Git repository and deploys the application as Kubernetes resources into the cluster. In doing so, it assumes the role traditionally held by Ansible.

The target state is versioned in Git. It is transparent to all stakeholders, can be reviewed and commented on during the review process, rather than being stored in a collection of playbooks that must be executed manually at a specific time.

The container runtime takes over what used to be the operating system configuration

Here’s an example from traditional operations: First, a VM is provisioned. Then, NGINX is configured on it using Ansible, directories are created, a configuration file is written, the service is started, and the application behind it is deployed. This work takes place at the operating system level.

In a cloud-native environment, a Kubernetes cluster is required, and the application is deployed as a Deployment or StatefulSet. At this level, the operating system no longer plays a role. This doesn’t just eliminate one tool; it also reduces configuration drift between machines. Configuration drift between machines disappears, and with it the phrase “but it’s different on that one node”—a phrase you’ve surely heard in any operational environment that’s been around for a while.

A quick aside: A GitOps operator is not the same as a Kubernetes operator

Both terms include the word “operator,” which often leads to misunderstandings, even within well-coordinated teams.

Kubernetes Operator is the umbrella term. An operator works with Kubernetes resources—often including custom resources—and reacts to their state. A typical example is an operator that monitors Node objects in the cluster and takes action as soon as a node is added or fails.

The GitOps Operator is a special case, or rather a subset of this. Argo CD works with its own Custom Resource Application, uses it to generate, for example, a Helm deployment, and deploys the result to the cluster.

Argo CD cannot respond to a node event, nor is that its purpose.

Anyone who equates the two terms expects the GitOps Operator to perform functions that it is structurally incapable of performing and subsequently looks for the cause in the wrong place.

What this means for uCloud

uCloud is delivered as a single Helm chart. Within it, each subproduct has its own Argo CD application, which in turn uses the Helm chart for that specific product. Argo CD itself deploys the charts and keeps them up to date. In practice, this means that all components required by a cloud platform can be installed in a single step. What subsequently runs in the cluster is first described as a declarative state in Git. A rollback thus becomes a revert. And anyone setting up a second environment does not need to piece together operational knowledge from the minds of those involved.

The platform is operated by its own management cluster on separate hardware. This is deliberately separated from the cloud platform it manages. After all, a tool that manages a platform should not be able to fail along with that platform.

What are the operational benefits?

Replacing two familiar tools with two others would not, in and of itself, represent progress. The key difference lies in the fact that both tasks are implemented within the same declarative layer. The target state is defined in a central location rather than distributed across two different systems. There is no longer a specific point in time when a playbook must be executed: The operator continuously compares the actual state with the desired state.

Anyone who wants to know what is running in an environment can review the defined state in the repository. A second environment is created from the same source, rather than from a copy that must subsequently be manually modified with adjusted variables.

For robust platforms, this is more than just a matter of convenience. Anyone who needs to provide an auditor with verifiable evidence of which state is defined in an environment and how it was versioned can refer to the repository and the documented change history, rather than having to reconstruct the state solely through discussions with the operations team.

Related links:

INIT_CONNECTION

Start your project.