Materia
  • Home
  • Blog
  • Quickstart
  • Documentation
  • FAQ

On this page

  • Quickstart
    • Create the base materia repo
    • Create the caddy component
    • Create the resources for Caddy
      • Create the Caddy container Quadlet resource
      • Create the data and config Volume resources
      • Create the config File resource
      • Create repository attributes
      • Create the repository manifest
      • Final results
    • Generate a Materia Plan
      • Configure materia’s settings
      • Run materia plan
    • Run materia update

Quickstart

In this quickstart we will install the caddy service on a host named ivy.

For attributes management we will use the built-in file vault, without encryption.

Create the base materia repo

Like any GitOps tool, Materia needs a source repository that describes the desired state for your nodes. For the sake of this quickstart we’re going to assume you already know how to create and push to a Git repository.

Create a git repository on your workstation named whatever you like; we’re going to assume it’s called materia-repo.

Inside the git repo, create the base directories:

mkdir components attributes

Finally, create a manifest file:

touch MANIFEST.toml

This manifest file will describe what components go on what hosts. We’ll get back to it shortly.

Create the caddy component

Components are what materia uses to refer to a service and its associated resources (config files,data files, etc). They are the basic building blocks of a Materia repository and are similar to an ansible role.

For this example we will create a component for the caddy service.

Components are organized by directories under the components/ directory. Create a directory for caddy:

mkdir components/caddy

Create the resources for Caddy

Caddy requires the following resources:

  • a Container Quadlet resource to run the caddy container
  • Two Volume Quadlet resources for caddy’s data and config volumes
  • a File resource for caddy’s config file
  • a Manifest file that describes the component’s metadata. All components have a Manifest file, even if it’s empty.

The next few steps are done in components/caddy:

cd components/caddy

Create the Caddy container Quadlet resource

Create a file caddy.container.gotmpl for the Caddy container Quadlet: touch caddy.container.gotmpl. We want to template attributes into the file later, so make sure the file ends with .gotmpl to designate it as a templated resource. Materia uses the standard Go Templating engine.

Using your editor of choice, insert the following lines into the file we just created:

[Unit]
Description=Caddy reverse proxy

[Container]
Image=docker.io/caddy:{{ .containerTag }}
ContainerName=caddy
AddCapability=NET_ADMIN
Volume=caddy-data.volume:/data
Volume=caddy-config.volume:/config
Volume={{ m_dataDir "caddy" }}/conf:/etc/caddy:Z

{{ snippet "autoUpdate" "registry" }}

# local web data
{{- if ( exists "localWeb" )}}
Volume={{ .localWeb }}:/srv/www:Z
{{- end }}
PublishPort=443:443


[Service]
ExecReload=podman exec caddy caddy reload --config /etc/caddy/Caddyfile

[Install]
# Start by default on boot
WantedBy=multi-user.target default.target

The text contained in brackets are Go Templating variables, e.g. Image=docker.io/caddy:{{ .containerTag }} would become Image=docker.io/caddy:latest if containerTag is set to latest.

Materia also includes some ease of use functions like exists and m_dataDir that are referred to as macros.

Materia also includes pre-made snippets of text such as "autoUpdate".

Create the data and config Volume resources

Create two files, caddy-config.volume and caddy-data.volume. Since these will not be templated files, they do not need to end in .gotmpl.

touch caddy-config.volume caddy-data.volume.

Both files should have the same content: ~ini [Volume]~

Create the config File resource

Now to create a Caddyfile for caddy’s config. We are going to create this in an subdirectory that will be bind-mounted into the container; this is to make it easier for Caddy to see when the Caddyfile changes on systemctl reload and to show off how subdirectories will be translated as-is from the repository to the target host.

Create the Caddyfile template:

mkdir conf/
touch conf/Caddyfile.gotmpl

Insert the following lines into your freshly created Caddyfile:

localhost {
    respond "Hello, world!"
}

In the real world most config files are simple and can be templated more directly i.e. ConfigOption = {{ .configValue }} but since Caddyfiles are more complicated and this is a quickstart, we’re going to just hard-code a configuration.

Create the Manifest resource

The last thing we need is a manifest file for the component. Create the file in the root of the component (i.e. materia-repo/components/caddy/)

touch MANIFEST.toml

Add the following content:

[Defaults]
containerTag = "latest"

[[Services]]
Service = "caddy.service"

The [Defaults] section is a TOML table describing default attribute values. In this case, the containerTag attribute is set to latest by default.

The [[Services]] section is a TOML array describing what services the component cares about. They can be either a part of the component or installed separately. In this case, the only service that is defined is the caddy.service. This means that when the component is installed materia will start the caddy.service unit, when the component is removed it will make sure the service is stopped, and if the service is detected as not-running when materia runs it will attempt to start it again.

For more details, such as how to set services to restart or reload when certain files are updated, see the component section of the manifest reference. Materia will restart services for containers and pods by default when their resources change, but for the sake of this quickstart we will be specific.

Create repository attributes

We’ve created the caddy component, but some of the resources in it use attributes. It’s time to setup our attributes engine and store some attributes.

By default, the file attributes engine will look for *.toml files named either vault.toml, attributes.toml, or the hostname of the node e.g. ivy.toml.

We will be setting up two file-based vaults in our repository: one that applies to all hosts (attributes/vault.toml) and one that only applies to ivy (attributes/ivy.toml).

Create the general vault

First we create the vault used by all hosts. Create a file named attributes/vault.toml:

touch attributes/vault.toml

Put the following content in it:

[global]
containerTag = "stable"

[components.caddy]
containerTag = "latest"

Attributes in the [global] section will be available to all hosts and all components.

Attributes in a [components.componentname] section will only be available to the component componentname.

With this file, all components with the containerTag attribute will have the value stable, except for the caddy component which will have latest.

Create the host vault

Next, create a vault that will only apply to components installed on ivy:

touch attributes/ivy.toml

Insert the following content in it:

[components.caddy]
localWeb = "/srv/www"

This means the localWeb attribute on ivy and only ivy will be set to “/srv/www”.

Create the repository manifest

We have a component and we have attributes to use with the component, now to put it all together in the repository manifest.

Open the materia-repo/MANIFEST.toml file and add the following content:

[Hosts.ivy]
components = ["caddy"]

Here we’ve defined the ivy host ([hosts.ivy]) and assign the caddy component to it: components = ["caddy"].

Final results

The final repository directory should like this:

materia-repo/
materia-repo/components/
materia-repo/components/caddy
materia-repo/components/caddy/conf
materia-repo/components/caddy/conf/Caddyfile.gotmpl
materia-repo/components/caddy/caddy.container.gotmpl
materia-repo/components/caddy/caddy-config.volume
materia-repo/components/caddy/caddy-data.volume
materia-repo/attributes/
materia-repo/attributes/ivy.toml
materia-repo/attributes/vault.toml
materia-repo/MANIFEST.toml

Push your git repository and logon to ivy for the next steps.

Generate a Materia Plan

Materia uses a plan-execute system for managing nodes. We can generate a plan ahead of time for validation purposes.

These steps will assume your git repository is at github.com/user/materia-repo and is public. If you need to use a private repository, look at the git configuration settings

Configure materia’s settings

Materia needs to know where your repository is. This can be done in a config file, but we’ll just use environmental variables:

export MATERIA_SOURCE__KIND=git export MATERIA_SOURCE__URL=https://github.com/user/materia-repo

Materia also needs to know what attributes engine you’re using and where they’re located.

export MATERIA_ATTRIBUTES=file export MATERIA_FILE__BASE_DIR=attributes

Run materia plan

Use the plan command to see what materia will do when you run it. The output should look something like this:

$ materia plan
Plan:
1. (caddy) Install Component caddy
2. (caddy) Install Container caddy.container
3. (caddy) Install Volume caddy-config.volume
4. (caddy) Install Volume caddy-data.volume
5. (root) Host Reload
6. (caddy) Start Service caddy.service

$

The output is deterministic and will make sure that your repository is valid. If there’s any missing attributes or other information not available to the host it will fail.

Run materia update

Finally, use the update command to update ivy to the desired state:

$ materia update
Plan:
1. (caddy) Install Component caddy
2. (caddy) Install Container caddy.container
3. (caddy) Install Volume caddy-config.volume
4. (caddy) Install Volume caddy-data.volume
5. (root) Host Reload
6. (caddy) Start Service caddy.service

$ ls /etc/containers/systemd/
caddy
$ ls -a /etc/containers/systemd/caddy
. .. caddy.container caddy-config.volume caddy-data.volume .materia_managed
$ ls -a /var/lib/materia/components/caddy
. .. .component_version conf/
$ systemctl is-active caddy.service
active
$