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/caddyCreate 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.targetThe 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.tomlPush 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
$