> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://rivet.chrisboulton.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://rivet.chrisboulton.com/_mcp/server.

# Manage an application

> Ship a systemd unit and a validated config file, and restart the service when the config changes.

This example installs a systemd service with a JSON config file. The config is checked before it replaces the old one, and the service restarts when the config changes. The files are in the repository at `examples/service/`: `main.hcl`, `config.json`, and `rivet-example.service`. The validator needs Python 3 on the host.

```hcl
file "directory" {
  path   = "/var/lib/rivet-example"
  ensure = "directory"
}

file "configuration" {
  path     = "/var/lib/rivet-example/config.json"
  source   = "config.json"
  validate = ["/usr/bin/python3", "-m", "json.tool", "{candidate}"]

  meta {
    depends_on = [file.directory]
    notify     = [service.example]
  }
}

file "unit" {
  path   = "/etc/systemd/system/rivet-example.service"
  source = "rivet-example.service"
}

service "example" {
  name          = "rivet-example"
  ensure        = "running"
  enabled       = false
  masked        = false
  on_change     = "restart"
  daemon_reload = "auto"

  meta {
    timeout = "30s"
  }
}
```

`source` paths are relative to the configuration directory. Their content is copied into the bundle, so the host doesn't need the original files.

## Compile and apply

On the disposable Ubuntu host from the [first walkthrough](/getting-started/quickstart):

```sh
umask 077
rivet facts --local > facts.json
rivet compile examples/service --facts facts.json --out service.bundle
rivet graph service.bundle
```

```text
Compiled 4 resources, digest a9b6b676f1371cdf4c3857fbb6c895a11fff5c78808493bf19f8c22fe1b6391e
file.directory
file.configuration
file.unit
service.example
file.configuration -> service.example (Notify)
file.directory -> file.configuration (DependsOn)
file.directory -> file.configuration (Expression)
file.unit -> service.example (Action("systemd.daemon_reload.v1"))
```

```sh
sudo rivet apply service.bundle --local
```

```text
Unchanged file.directory []
Changed file.configuration [ensure]
Changed file.unit [ensure]
Changed service.example [state]
Action: daemon_reload
```

The directory already exists from the first walkthrough. You can also compile on another machine: copy `facts.json` there, compile, and copy the bundle back.

## What happened

* **Validation before replacement.** Rivet wrote the new JSON to a temporary file next to its destination and ran the validator on it, replacing `{candidate}` with that file's path. Only after the validator succeeded was the file moved into place. If validation fails, the old file stays untouched.
* **Daemon reload.** Because the unit file lives in `/etc/systemd/system` and the service uses `daemon_reload = "auto"`, Rivet ordered the unit file before the service and ran `systemctl daemon-reload` once before starting it. That's the `Action: daemon_reload` line.
* **No redundant restart.** The config change queued a restart, but the service was starting for the first time, so the start satisfied it. Later config changes restart the service.

If the run had been interrupted after the config changed but before the restart, the next apply would still restart the service. See [recovery](/reference/recovery).

## Extend it

Add a [package](/resources/package) for the application's dependencies, and a [user](/resources/user) and [group](/resources/group) for a service account. Add `depends_on` where order matters, for example a service that needs an installed package. Naming an account in a command's `user` field does not by itself order the command after the account. See [relationships](/language/relationships).