Deploying the MariaDB Privacy-First Stack Anywhere with Terraform

In my previous post, I introduced the MariaDB Privacy-First Stack.

Nextcloud for collaboration, Passbolt for passwords and secrets, and MariaDB Server for the data.

Simple enough.

But after explaining what the stack is, the next question is usually very practical:

Where can I deploy it?

And the answer should not be:

On the cloud provider the template was written for.

That would be a strange way to talk about digital sovereignty.

If the architecture is really meant to give you more control, you should also have some freedom regarding where it runs. Maybe your organization already uses AWS. Maybe it has an Azure agreement. Maybe your team prefers Google Cloud. Perhaps you want to use Oracle Cloud Infrastructure, or you operate an OpenStack environment in your own datacenter.

The applications should not decide that for you.

So, here is a Terraform project for the Privacy-First Stack that can deploy the same stack on several cloud providers.

At the moment, the repository supports:

  • Amazon Web Services
  • Microsoft Azure
  • Google Cloud Platform
  • Oracle Cloud Infrastructure
  • OpenStack

One stack.

Several providers.

And, more importantly, one architecture that you can inspect, modify and operate yourself.

Why Terraform?

Of course, it is possible to create a virtual machine manually.

Log in to the cloud console. Click a button. Select an image. Choose a network. Add a firewall rule. Create a disk. Forget which option you selected. Repeat everything six months later and obtain something slightly different.

That works.

Until you need to reproduce it.

Terraform gives us a more useful approach. The infrastructure is described as code. The network, compute instance, security rules and deployment settings are written in files that can be reviewed and stored in Git.

It does not make infrastructure magically correct.

Please do not confuse automation with correctness.

Terraform will deploy exactly what you describe, including your mistakes. It is very efficient like that.

But having the infrastructure in code makes those mistakes visible, reviewable and reproducible. That is already much better than a collection of screenshots from a cloud console and a document called deployment-notes-final-v2.

The project uses provider-specific Terraform configurations around a common stack module. Each cloud has its own authentication model, resource names and networking concepts, but the applications installed at the end remain the same.

The same stack on different clouds

The repository contains a directory for every supported provider:

privacy-first-stack-terraform/
├── aws/
├── azure/
├── gcp/
├── oci/
├── openstack/
└── modules/
    └── native_stack/

You select the provider by entering its directory.

For example:

git clone https://github.com/lefred/privacy-first-stack-terraform.git
cd privacy-first-stack-terraform/aws

Or:

cd privacy-first-stack-terraform/azure

The basic Terraform workflow is then the same:

cp terraform.tfvars.example terraform.tfvars

Edit terraform.tfvars with the values required by your cloud account and deployment:

terraform init
terraform plan
terraform apply

I strongly recommend paying attention to terraform plan.

Don’t just press Enter because the output contains a lot of text.

The plan is where Terraform tells you what it intends to create, replace or delete. Reading it is less exciting than watching resources appear in a dashboard, but it is also less exciting than accidentally deleting them.

Boring is good.

Especially for infrastructure.

Consolidated or distributed

Every provider supports the same deployment_mode variable.

The default mode is:

deployment_mode = "consolidated"

In consolidated mode, MariaDB Server, Nextcloud and Passbolt run on the same virtual machine.

This is convenient for testing, demonstrations and small lab environments. It uses fewer cloud resources, costs less and makes it easy to understand how the pieces fit together.

There is also a distributed mode:

deployment_mode = "distributed"

In that mode, Terraform creates three virtual machines:

  • one for MariaDB Server;
  • one for Nextcloud;
  • one for Passbolt.

The Nextcloud and Passbolt servers communicate with MariaDB through the private cloud network. The database server does not need a public IP address.

That is a more realistic separation for larger deployments, but it also means three compute instances, three boot volumes and more infrastructure to operate.

Please don’t select distributed mode just because it sounds more serious.

Architecture is not a competition.

Start with what you need. Understand it. Measure it. Then add separation, redundancy and high availability where they solve an actual problem.

Existing network or new network

The configurations can create the network resources needed by the stack, but they can also reuse existing infrastructure.

That is important because many organizations already have their own network layout, routing rules, VPN connections, address ranges and security requirements.

Depending on the provider, you can supply an existing subnet, network, resource group, image or availability domain. When these values are omitted, Terraform can create or discover the corresponding resources.

For example:

  • AWS can reuse an existing subnet or create a VPC and public subnet.
  • Azure can reuse a resource group and subnet or create a resource group, virtual network and subnet.
  • Google Cloud can reuse a subnetwork or create a custom VPC network.
  • OpenStack can reuse a tenant network and image.
  • OCI can reuse a subnet, image and availability domain, or discover and create the required resources.

Reusing a subnet does not mean Terraform becomes responsible for everything around it.

Routing, internet access and NAT remain your responsibility when you bring existing network infrastructure. During the first boot, the instances need outbound HTTPS access to install the required software.

Again, automation does not remove the need to understand the network.

It only makes misunderstanding it repeatable.

Deploying on AWS

For AWS, enter the aws directory:

cd privacy-first-stack-terraform/aws
cp terraform.tfvars.example terraform.tfvars

Configure the required variables, authenticate with AWS and run:

terraform init
terraform plan
terraform apply

The configuration can create the VPC, subnet, internet gateway, route table, security groups and compute resources required by the stack. Alternatively, you can supply an existing subnet.

I recorded a complete deployment demonstration:

The goal of the video is not to teach every AWS service.

That would be a much longer video.

The goal is to show how the Terraform configuration provisions the infrastructure and how the Privacy-First Stack becomes available on it.

Deploying on Microsoft Azure

Azure authentication can be initialized using the Azure CLI:

az login

You can retrieve the active subscription ID with:

az account show --query id --output tsv

Then prepare the Terraform configuration:

cd privacy-first-stack-terraform/azure
cp terraform.tfvars.example terraform.tfvars

terraform init
terraform plan
terraform apply

The Azure configuration can create the resource group, virtual network, subnet, network security rules and virtual machine, or it can use existing Azure resources.

The deployment is demonstrated here:

The cloud vocabulary changes.

AWS has VPCs and security groups. Azure has virtual networks, subnets and network security groups.

The applications don’t care.

Nextcloud still needs its database. Passbolt still needs its database. MariaDB Server still stores the relational data.

Deploying on Google Cloud

On Google Cloud, you first need a project.

You can initialize the Google Cloud CLI and authenticate with:

gcloud init
gcloud auth application-default login

Select your project:

gcloud config set project privacy-first-stack

The Compute Engine API must also be enabled:

gcloud services enable compute.googleapis.com \
  --project=privacy-first-stack

Then use the GCP Terraform directory:

cd privacy-first-stack-terraform/gcp
cp terraform.tfvars.example terraform.tfvars

terraform init
terraform plan
terraform apply

The GCP example can create a custom VPC and regional subnet or reuse an existing subnetwork.

You can watch the deployment here:

The project name used in the example is privacy-first-stack, but naturally you should use your own project ID.

Cloud providers already contain enough resources called test, demo and new-project.

We don’t need to add more confusion.

Deploying on Oracle Cloud Infrastructure

OCI uses its own set of identifiers and authentication details.

For API-key authentication, the Terraform provider can require values such as:

  • the tenancy OCID;
  • the user OCID;
  • the compartment OCID;
  • the API-key fingerprint;
  • the region;
  • the private key or its path.

Then the usual workflow applies:

cd privacy-first-stack-terraform/oci
cp terraform.tfvars.example terraform.tfvars

terraform init
terraform plan
terraform apply

The OCI configuration can create a VCN, subnet, internet gateway and route table. It can also discover an Ubuntu image and availability domain when those values are not supplied.

As with the other providers, inspect the plan before applying it.

OCIDs are not particularly pleasant to read.

That is not an excuse to ignore them.

This is a deployment demo on OCI:

Deploying on OpenStack

OpenStack is especially interesting in a Privacy-First Stack context because it can run in a private cloud operated by your own organization or by a provider you select.

Use the OpenStack directory:

cd privacy-first-stack-terraform/openstack
cp terraform.tfvars.example terraform.tfvars

terraform init
terraform plan
terraform apply

The Terraform configuration can create the network, subnet and router required by the deployment, or it can reuse an existing tenant network.

This means the same stack can run in a large public cloud or in an OpenStack environment under your own operational control.

That is what portability should look like.

Not a marketing slide with five cloud logos.

Code you can actually run.

Terraform creates the infrastructure first

There is an important detail to understand.

When terraform apply completes, the virtual machine exists.

That does not necessarily mean Nextcloud and Passbolt have finished installing.

The applications are installed using cloud-init during the first boot. Depending on the cloud, machine size, package mirrors and network performance, that process may continue for several minutes after Terraform has finished.

You can follow its progress on the instance:

sudo tail -f /var/log/privacy-stack-install.log

A completion marker is created when the installation finishes:

/var/lib/privacy-stack-ready

You can check it with:

sudo test -f /var/lib/privacy-stack-ready \
  && echo "Privacy-First Stack is ready"

This distinction matters.

Terraform provisions infrastructure.

Cloud-init configures the applications.

They are related steps, but they are not the same step.

Accessing the applications

In consolidated mode, both applications use the public IP address of the same virtual machine:

  • Passbolt is available on ports 80 and 443.
  • Nextcloud is available on port 8080.

For example:

https://PUBLIC_IP/
http://PUBLIC_IP:8080/

In distributed mode, Passbolt and Nextcloud each have their own public IP address, while MariaDB remains on the private network.

The Terraform outputs provide the addresses created by the selected provider.

Passbolt is configured with a self-signed TLS certificate. Its subject alternative names include the detected public IP address and, when configured, the Passbolt domain.

Your browser will warn you about this certificate.

That is expected in the lab configuration.

For production, use a certificate issued by a trusted certificate authority or place a properly configured reverse proxy or managed TLS load balancer in front of the application.

A browser warning is not a TLS strategy.

Clicking “accept permanently” on every workstation is not one either.

Security still belongs to you

The Terraform code creates a usable environment.

It does not mean the resulting deployment is automatically production-ready.

Review at least the following:

  • the CIDR ranges allowed to access SSH;
  • the networks allowed to reach the applications;
  • TLS certificates and domain names;
  • operating-system and application updates;
  • MariaDB backups;
  • Nextcloud data backups;
  • Passbolt server-key backups;
  • monitoring and alerting;
  • log retention;
  • recovery procedures;
  • Terraform state protection.

The generated cloud-init data and Terraform state may contain sensitive values. State should therefore be stored in an encrypted backend with tightly restricted access.

Do not commit terraform.tfvars or a local Terraform state file containing passwords to a public repository.

Yes, people still do that.

No, deleting the file in a later commit is not enough.

Git remembers.

The installer also generates the Passbolt OpenPGP server key pair and stores it under:

/etc/passbolt/gpg

Back up both server-key files securely. Losing the private server key can interfere with Passbolt authentication and encrypted server operations.

And, of course, test the backup.

A backup that nobody has restored is only a theory.

Multi-cloud does not mean running everywhere

There is another expression that needs some care: “multi-cloud”.

Supporting several providers does not mean you must deploy the stack simultaneously on all of them.

That would create more accounts, more networks, more credentials, more invoices, more monitoring systems and more ways to be called during the night.

In this project, multi-cloud means choice.

The same architecture can be deployed on different infrastructure providers. You can select the environment that fits your technical, geographical, contractual and operational requirements.

It also gives you a useful escape route.

The Terraform resources are provider-specific, naturally, but the stack itself is still based on Nextcloud, Passbolt, MariaDB Server and Ubuntu compute instances. Your applications and data model are not replaced by proprietary cloud services at every layer.

Migration between providers is still work.

There is no magic terraform move-everything-to-another-cloud command.

You need to plan data transfer, DNS changes, certificates, downtime, database consistency and rollback.

But having visible infrastructure code and familiar open-source applications is a much better starting point than discovering that your architecture depends on twenty-seven proprietary services with similar names.

Conclusion

The MariaDB Privacy-First Stack is about control.

Control of collaboration data with Nextcloud. Control of credentials and secrets with Passbolt. Control of relational data with MariaDB Server.

The Terraform project extends that idea to the infrastructure layer.

AWS, Azure, Google Cloud, OCI or OpenStack.

Choose the environment that makes sense for you.

The provider supplies the compute, network and storage resources. Terraform describes them. Cloud-init installs the stack. Nextcloud and Passbolt use MariaDB Server for their relational data.

No magic.

Just open-source components, visible infrastructure code and an architecture you can explain.

Clone the Privacy-First Stack Terraform repository, choose a provider and build a lab.

Run it.
Inspect it.
Break it.
Restore it.
Destroy it.
Then deploy it again somewhere else.

This is a starting point, not the final destination; for example, a critical deployment may require MariaDB replication or Galera Cluster, redundant application nodes, and more.

Happy testing, and enjoy this Security First Stack by MariaDB Server and friends!