Charmed SD-Core available through Terraform modules

Awesome! But… what is Charmed SD-Core?

Let’s say you run a farm, a hospital, a smart factory or an academic campus and that you want or need 5G connectivity for it. Traditional mobile networks, delivered by vendors like Nokia, Ericsson or Huawei are very expensive and complicated to deploy and maintain. It usually takes a team of experienced network engineers and a lot of time to get the network up and running. To make things even worse, at Day 2, when you want to scale up your network, it turns out that there’s pretty much zero flexibility when it comes down to equipment choice - you are limited to the software and hardware delivered by the vendor you picked on Day 0.

If you are a big MNO like Vodafone or Telefónica you probably don’t care about all these “details”, but for average Joes, these “details” become a serious limitation. How do we get around these limitations then?

Well, luckily, Canonical and the Open Networking Foundation save the day, by bringing you the

Charmed SD-Core. Charmed SD-Core is a secure, reliable and observable distribution of the Open Networking Foundation’s SD-Core project. It is a fully functional 5G core network, responsible for the 5G Network’s control operations: authentication, IP assignment, policy management, Quality of Service, slice management and more. The project combines ONF’s 5G expertise with Juju model driven operations.

Charmed SD-Core is part of a bigger initiative called Charmed 5G, which aims at delivering an entire open source 5G network (both the Core and the Radio Access Network part).

What can be more exciting than that? Hmm, perhaps the fact that it’s all free?

Terraform, you say? Why not use Juju bundles?

Corrent. Traditionally Juju provides bundles to represent big and complex deployments involving multiple applications and integrations between them. One might think that an open source 5G Core network, made of 15 charms and over 30 integrations between them, would be a perfect candidate for bundling. That is true, because bundles do a great job on Day 1. But it is also true that on Day 2, managing a bundle is simply impossible, because at that point… the bundle doesn’t exist anymore. This means that the operator can’t upgrade the bundle when the new version of Charmed SD-Core is released.

A solution to this problem is provided by Hashicorp’s Terraform. Terraform provides a declarative way of managing the resources (environments, applications, integrations) through its huge library of providers (including a Juju provider). It also gives the possibility of using resources created by one module in another module. Terraform also tracks the status of the deployment and allows applying changes to it (updates, upgrades, or even removal of the entire deployment). Using Juju and Terraform together allows for building a complete and consistent solution for Day 1 and Day 2 operations.

OK, so what exactly has been done?

In order to provide users with maximum flexibility when composing their own deployments and to keep the user experience as close to the original Juju concepts as possible, in the Telco team, we’ve decided to create Terraform modules for SD-Core charms using the granular approach. What does this mean in practice?

Base modules

Each SD-Core charm provides its own base Terraform module, representing the charm itself along with its configuration options and possible integrations. Deploying the base module alone would be equivalent to deploying the charm using the traditional juju deploy.

To minimize the maintenance effort, we have decided to store base modules in the same repository the charm code lives in. The prize for convenience in case of introducing/removing charm integrations or branching was not being able to use the public Terraform registry for publishing our modules and sharing them with the world. The reason for that is that Terraform has very strict rules regarding the repository naming pattern, which we don’t meet. Fortunately, Terraform allows importing modules directly from GitHub, so the prize is bearable.

The general structure of the Charmed SD-Core base Terraform module looks as follows:

├── terraform
│   ├──
│   ├──
│   ├──
│   ├──
│   └──

Going from the top, the is responsible for defining the Juju application which will be deployed to the model by the module. It defines the name of the Juju model the application will be deployed to, the name of the application in the model and the charm to deploy. It is also responsible for passing the user configuration to the charm or controlling the number of units the application should have. The example below shows one of such base modules created for the SD-Core:

resource "juju_application" "amf" {
  name  = var.app_name
  model = var.model_name

  charm {
    name    = "sdcore-amf-k8s"
    channel =
  config = var.config
  units  = 1
  trust  = true

Next, file is used to expose the information about the module (charm) to other Terraform modules. This feature of Terraform comes in very handy when putting multiple base modules together in order to create a bigger deployment. We’ll describe it in more detail in the next paragraph. In the Charmed SD-Core modules, we use to inform other modules about the deployed application name and the possible integrations it provides or requires.

output "app_name" {
  description = "Name of the deployed application."
  value       =

# Required integration endpoints

output "fiveg_nrf_endpoint" {
  description = "Name of the endpoint used to integrate with the NRF."
  value       = "fiveg_nrf"


# Provided integration endpoints

output "metrics_endpoint" {
  description = "Exposes the Prometheus metrics endpoint providing telemetry about the AMF instance."
  value       = "metrics-endpoint"


In order for Terraform to know which provider should be used to handle the module, it should be defined in the file. In our case, it’s the Juju provider of course:

terraform {
  required_providers {
    juju = {
      source  = "juju/juju"
      version = "~> 0.11.0"

Last but not least, we have the file. As the name indicates, it is used to customize the deployment through a bunch of user-defined variables. One variable that requires special attention is the config variable. If you’d like to pass any configuration options to the charm, that’s the way to go. Charm config should be passed in the form of the dictionary of strings. While this solution is definitely troublesome (it requires the user to know the allowed configuration options for the charm) it is also the easiest way to preserve the module compatibility with multiple charm versions even if the config changes. A typical example of the in Charmed SD-Core is shown below.

variable "model_name" {
  description = "Name of Juju model to deploy application to."
  type        = string
  default     = ""

variable "app_name" {
  description = "Name of the application in the Juju model."
  type        = string
  default     = "amf"

variable "channel" {
  description = "The channel to use when deploying a charm."
  type        = string
  default     = "1.3/edge"

variable "config" {
  description = "Application config. Details about available options can be found at"
  type        = map(string)
  default     = {}

Root modules

Now that we have base modules covered, it’s time to finally get to the main course - the root modules.

While the base modules allow deploying the individual components, are fully customizable and reusable, in fact, they don’t give any added value compared to the traditional way of deploying charms. In some cases we may even find them less convenient to use. One could ask why do we need them then? The answer to this question is in the solution to the problem that the Juju Terraform module and this article try to address - replacing the Juju bundles. In the Terraform world, the equivalent to Juju bundles are the root Terraform modules. And those are built using the base modules. Let’s take a closer look at the Charmed SD-Core root modules.

As already mentioned before, when designing Terraform modules for the SD-Core charms, we wanted to give the users as much freedom in shaping their deployments as possible. In practice, any user can compose their own root module including a custom selection of the SD-Core charms. Independently from that, our Team has also designed and created 3 root modules covering the most common deployment types:

  • All-in-one - a single root module containing both the User Plane and the Control Plane functions
  • User Plane - a root module for deploying the User Plane part of the 5G Core
  • Control Plane - a root module for deploying the Control Plane part of the 5G Core

If you inspect the structure of the Charmed SD-Core root modules, one thing you will immediately notice is that it’s exactly the same as in the base modules. That’s absolutely correct and also that’s why we’re not going to spare time to explain it again. There are a couple of important nuances we should highlight though.

The first important difference between the base module and the root module is in the and involves the scope of the deployment the module covers. As we said before, the base modules only focus on a single application. The root module, on the other hand, puts a bunch of applications represented by individual base modules in the context of the Juju environment represented by a Juju model. As a gluing point, it’s also responsible for defining the integrations between the applications in the deployment (here’s where the output values from the base modules become extremely useful).

The second difference comes in the file. While the general purpose is the same - expose information about the module to other modules, the scope is different. For the base modules, we would expose charm’s every possible integration. In the root modules, we expose only those that can be used to create cross-model integrations.

The cherry on top of the Charmed SD-Core root Terraform modules is the built-in support for the Canonical Observability Stack (COS). As mentioned in the beginning of this article, Charmed SD-Core is fully observable. As part of our work, we have prepared a set of Terraform modules modeling the COS and integrated them with the Charmed SD-Core root Terraform modules. Deploying COS is optional and can be easily enabled by switching one variable in the root module’s file.

Sounds good to me! How do I put my hands on it?

First of all, thank you for getting that far!

Let’s try to quickly recap the key things you should take away from this article:

  1. If you need or want to deploy your own, private 5G Core network, Charmed 5G is your way to go.
  2. There’s no better way to deploy Charmed 5G than using our root Terraform modules. They are simple, they are flexible and they have you covered on the Day 2 just as well as on the Day 1.
  3. Advanced observability provided by the Canonical Observability Stack comes as a part of the package and can be easily enabled at any time.

If any of the above sound appealing to you, we strongly encourage you to explore the Charmed 5G documentation page, especially our tutorials section which will guide you through your first deployments of the Charmed 5G.

Once you’re done, please make sure to share your feedback with us. Well be very happy to hear the good one and twice as happy to hear constructively bad one :slightly_smiling_face:

You can chat with us on Matrix or create an issue on GitHub directly.

Good luck!