# Build your first IOC instance
This tutorial walks you end to end through turning a support module
definition into a running EPICS IOC startup script, using the `motorSim`
simulated‑motion sample that ships with `ibek`. By the end you will have:
1. read a *support definition* file and understood what it declares,
2. generated a JSON *schema* for authoring instances,
3. written an *IOC instance* file,
4. generated the *runtime* files an IOC needs to boot, and
5. compared the produced `st.cmd` against the known‑good sample.
Every command here is real and tested. Inside a generic IOC container the
`ibek` command is already on the `PATH`, so type the commands exactly as
shown. If you are following along in a checkout of this repository instead,
prefix each command with `uv run` (the first run rebuilds the virtual
environment, so give it a moment).
The bare file names below assume the container layout, where the support and
instance files sit in the working directory. In a checkout they live under
`tests/samples/support/`, `tests/samples/iocs/` and `tests/samples/outputs/`,
so either `cd tests/samples` first and prefix the names with `support/`,
`iocs/` and `outputs/`, or pass the full paths.
## The three phases
`ibek` is used at three different points in an IOC's life. Keep this map in
mind as you go — each step below belongs to one of these phases.
```{mermaid}
flowchart LR
A[Container build phase
support modules + their
.ibek.support.yaml defs
baked into the image] --> B[Authoring phase
generate schema,
write the instance YAML]
B --> C[Boot phase
runtime generate ->
st.cmd + runtime db,
then the IOC starts]
```
Steps 1–3 are **authoring**: a developer does them once to create an IOC
instance. Step 4 belongs to the **boot phase**: when an IOC container starts
it regenerates these files. Production containers do this with the newer
`generate2` subcommand; this tutorial uses the simpler legacy single‑instance
`generate`, which is enough for one instance file. The support modules
referenced in step 1 were compiled and their definition files collected during
the earlier **container build phase**.
## Step 1 — start from a support definition file
A support module ships an `*.ibek.support.yaml` file that declares one or
more **Entity Models** — the building blocks you can later instantiate in an
IOC. Open the sample:
`tests/samples/support/motorSim.ibek.support.yaml`
```{literalinclude} ../../tests/samples/support/motorSim.ibek.support.yaml
:language: yaml
:lines: 6-57
```
The top‑level `module` names the support module, and `entity_models` is the
list of models it provides. The first model above, `simMotorController`,
declares its `parameters` (typed inputs such as `controllerName`, `P`,
`numAxes` and a `port` of `type: object`), the `pre_init` startup‑script
snippet it contributes, and the `databases` and `pvi` it brings. A second
model, `simMotorAxis`, follows further down the file.
For the full meaning of every field, see the
[support YAML reference](../reference/support-yaml.md).
## Step 2 — generate the entities schema
To author an instance with editor autocompletion and validation, turn the
support definitions into a JSON schema:
```bash
ibek ioc generate-schema \
motorSim.ibek.support.yaml asyn.ibek.support.yaml \
--no-ibek-defs \
--output motorSim.ibek.ioc.schema.json
```
The `--no-ibek-defs` flag tells `ibek` to build the schema **only** from the
files you list, instead of also pulling in the support definitions bundled
into a generic IOC container. That is exactly what you want when you are
working outside a container (as in this tutorial) — you supply the
definitions yourself.
We pass both `motorSim` *and* `asyn` because the instance we are about to
write uses an entity from each module; the generated schema must know about
every model the instance references.
## Step 3 — author the IOC instance
An IOC instance file lists the concrete entities you want, each one an
instance of a model from step 1. Here is the sample:
`tests/samples/iocs/motorSim.ibek.ioc.yaml`
```{literalinclude} ../../tests/samples/iocs/motorSim.ibek.ioc.yaml
:language: yaml
```
Three things to notice:
- **`ioc_name`** identifies the IOC. Here it uses the Jinja value
`{{ ioc_yaml_file_name }}`, which `ibek` fills in from the instance
file's own name.
- **`type:`** on each entity selects a model using `module.model`
notation — `asyn.AsynIP`, `motorSim.simMotorController`,
`motorSim.simMotorAxis`.
- **Object references** wire entities together. The `simMotorController`'s
`port` is set to `controllerOnePort` (the `name` of the `asyn.AsynIP`
entity above it), and each `simMotorAxis` sets `controller` to
`controllerOne` (the controller's `controllerName`). `ibek` validates
these references, so a typo is caught early.
The first line is a `yaml-language-server` comment pointing at the schema
from step 2 — that is what gives you autocompletion in a YAML‑aware editor.
For the field‑by‑field details see the
[IOC YAML reference](../reference/ioc-yaml.md), and for how the `{{ ... }}`
templating is evaluated see the
[Jinja context reference](../reference/jinja-context.md).
## Step 4 — generate the runtime files
Now produce the files the IOC needs to boot. The `generate` subcommand used
here is the legacy single‑instance form (production containers run `generate2`
at startup); it is the simplest way to follow along with one instance file:
```bash
ibek runtime generate \
motorSim.ibek.ioc.yaml \
motorSim.ibek.support.yaml asyn.ibek.support.yaml \
--no-pvi \
-o /tmp/runtime
```
The first argument is the instance file; the remaining arguments are the
support definition files for **every** module the instance uses — again both
`motorSim` and `asyn`. The `-o` option chooses the output folder. The
`--no-pvi` flag skips generating PVI screens, which `ibek` would otherwise
write to `/epics/opi` regardless of `-o` — a path that only exists inside a
container, so the command fails without it when run from a checkout.
```{note}
Inside a real IOC container these support definition files live in a known
location that was populated during the container build phase, and the
EPICS environment variables that set the IOC and runtime paths are already
exported. In this tutorial's sandbox those variables are unset, so the
absolute paths in the generated script differ slightly — the structure is
identical.
```
## Step 5 — inspect the generated `st.cmd`
Look at `/tmp/runtime/st.cmd`. It should match the tested sample at
`tests/samples/outputs/motorSim/st.cmd`:
```{literalinclude} ../../tests/samples/outputs/motorSim/st.cmd
:language: shell
```
Trace it back to your inputs:
- The `AsynIPConfigure(...)` line came from the `asyn.AsynIP` entity.
- `motorSimCreateController(controllerOne, 4)` is the `simMotorController`'s
`pre_init` snippet, with `{{controllerName}}` and `{{numAxes}}` filled in.
- The block of `motorSimConfigAxis(...)` lines are the `post_init` snippets
of each `simMotorAxis`, in the order you declared them.
Alongside `st.cmd` you will also find `ioc.subst`, the substitution file that
expands into the runtime EPICS database. Together these are everything the
IOC binary needs to start.
## Where to go next
- The [CLI reference](../reference/cli.md) documents every command and option
used above (`ibek ioc generate-schema`, `ibek runtime generate`, and more).
- The [support YAML](../reference/support-yaml.md) and
[IOC YAML](../reference/ioc-yaml.md) references describe the two file
formats in full.
- The [Jinja context reference](../reference/jinja-context.md) lists the
variables and helpers available inside `{{ ... }}` templates.
- For the bigger picture of how these pieces fit together, read
[the overview](../explanations/overview.md).