You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/cli/features/profile-authoring.mdx
+84Lines changed: 84 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -32,6 +32,90 @@ Profile files use **JSONC** (JSON with Comments): standard JSON plus `//` line c
32
32
For an overview of what profiles are and how they compose with groups, see [Profiles & Groups](/cli/features/profiles-groups).
33
33
</Tip>
34
34
35
+
## How `extends` Works
36
+
37
+
`extends` lets your profile build on top of an existing one. Instead of writing everything from scratch, you pick a base and only specify what you want to add or change.
38
+
39
+
```json
40
+
{
41
+
"extends": "default",
42
+
"filesystem": {
43
+
"allow": ["/opt/my-tools"]
44
+
}
45
+
}
46
+
```
47
+
48
+
This profile inherits everything from `default` and adds one extra directory. The base profile is unchanged.
49
+
50
+
### What happens when profiles are combined
51
+
52
+
When nono loads a profile that extends a base, it merges the two together. Most fields fall into one of two patterns:
53
+
54
+
**List fields grow.** Things like allowed paths, denied commands, and security groups are combined — the base list and your list are joined together. Duplicates are removed automatically. You can always *add* to a list, but you cannot remove something a base already includes.
55
+
56
+
**Single-value fields can be overridden.** Things like `binary`, `allow_gpu`, `workdir`, and all `security.*` settings work differently: if you set them in your profile they replace the base value, and if you leave them out you inherit whatever the base had.
57
+
58
+
A few fields have their own specific rules:
59
+
60
+
| Field | How it works |
61
+
|-------|----------|
62
+
|`network.block`| Sticks once set. If any base sets this to `true`, your profile cannot set it back to `false`. |
63
+
|`open_urls`| Your value replaces the base entirely. Leave it out to inherit the base value. |
64
+
|`network.credentials`| Leave it out to inherit. Set it to `[]` to explicitly disable inherited credentials. Otherwise your list is combined with the base. |
65
+
|`network.network_profile`| Leave it out to inherit. Set it to `null` to explicitly clear the inherited proxy profile. |
66
+
|`hooks`, `env_credentials`, `environment.set_vars`| Merged as key-value maps. Your keys win over the base on any conflict. |
67
+
68
+
### Extending multiple profiles
69
+
70
+
You can extend more than one profile at once by passing an array:
71
+
72
+
```json
73
+
{ "extends": ["team-base", "python-tools"] }
74
+
```
75
+
76
+
The profiles are merged left-to-right, then your profile is applied on top. So for any single-value field where the bases conflict, the **rightmost** one wins. List fields are always combined from all of them.
77
+
78
+
```
79
+
team-base → python-tools → your profile
80
+
(your settings win)
81
+
```
82
+
83
+
### What if two bases share a common ancestor?
84
+
85
+
If you extend profiles A and B, and both of them extend a common base C, that's fine. List entries from C end up in the final profile once — duplicates are removed automatically. For single-value fields, the same rightmost-wins rule applies: if A and B both set the same field, B's value is what you get (assuming B is listed second in the array).
86
+
87
+
The one thing to watch out for is if A and B intentionally set the same single-value field to *different* values, because only one can win and it might not be obvious which. If you're unsure what you're actually getting, run `nono profile show my-profile` to see the resolved result.
88
+
89
+
### The `default` profile is always included
90
+
91
+
Every profile automatically gets the `default` built-in merged in, even if you don't write `extends` at all. You don't need to add `"extends": "default"` — it's already there.
92
+
93
+
If you want to remove a group that `default` includes, use `groups.exclude`:
94
+
95
+
```json
96
+
{
97
+
"groups": {
98
+
"exclude": ["dangerous_commands"]
99
+
}
100
+
}
101
+
```
102
+
103
+
### Checking what your profile actually resolves to
104
+
105
+
Inheritance can stack up. Use these commands to see the final result after all merging is done:
106
+
107
+
```bash
108
+
# Show the fully resolved profile
109
+
nono profile show my-profile
110
+
111
+
# See exactly what changed compared to default
112
+
nono profile diff default my-profile
113
+
```
114
+
115
+
<Note>
116
+
Inheritance chains are capped at 10 levels. If you hit this limit, nono will fail with a `ProfileInheritance` error — that's usually a sign the profile structure needs simplifying.
117
+
</Note>
118
+
35
119
## Generating a Profile
36
120
37
121
Use `nono profile init` to scaffold a new profile:
0 commit comments