Linode inside the project and environment boundary
A Linode account is flat: every project sits in one pile and can see the others.
The only thing that separates them is tags. So every resource here carries two
tags — the project tag says whose it is, the env tag says which environment it is —
and the PreToolUse hook refuses any write that does not satisfy both. A few types
(VPC, database, object storage…) cannot carry tags on the API; those use an
ownership ledger in the repo instead.
linode-cli stays the main tool, unwrapped. lingate only covers what
linode-cli has no concept of: project, environment, and the ledger.
The rule that matters most
Read freely; write only with the right project AND the right env. Concretely:
- A new resource always gets
--tags <project>and--tags <env>. Miss either and it is born outside the fence, and from then on no command can touch it. - Never write to a resource tagged with another project. No exceptions, even when the user says "just fix it" — ask again and let them run it themselves.
- Cross-env is an error, not a detail. A command running in
stagingmust not touch a resource taggedprod. To work in another env, say so on the command line —LINODE_ENV=prod linode-cli …— and ask the user first. - One resource, one environment. Unless the project enables
allowSharedEnvs— then one box may serve two envs to save cost, but every write to it asks when the other env is protected. Never add a second env tag yourself; uselingate adopt.
Do not
- Run
linode-cli … createwithout--tags. No tag, no owner. - Use
linode-cli <group> update <id> --tags xto "change a tag" — it is a PUT that replaces the whole tag array. To add a tag,lingate adopt; never write the update yourself. - Adopt an untagged resource on your own. It may belong to another project. Ask
the user first, then
lingate adopt <group> <id> --env <env> --yes. --root_pass <literal password>→ the value lands in the transcript and must be rotated at once. Useopgate exec; seereferences/secrets.md.- Print a kubeconfig / DB credential / object-storage key to stdout. Send it straight to a git-ignored file or into 1Password.
- Abbreviate flag names (
--tagfor--tags). The CLI accepts it and the hook understands it, but whoever reads the command later will not — spell it out. - Pass ids or tags through shell variables (
--linode_id $ID,--tags $TAGS), or wraplinode-cliin an unfamiliar wrapper. What the hook cannot read it refuses or asks about — write ids and tags literally on the command line. - Chain a ledger-type create (VPC, database…) with another command via
&&/;, or redirect its stdout. Run it alone with--jsonso the ledger records the right id. - Guess when refused. The hook always states the correct command — read it and follow it; do not look for a way around.
Running linode-cli
- Syntax:
linode-cli <group> <action> [id...] [--flags].linode-cli commandslists the groups;linode-cli <group> <action> --helpis the reference — use it instead of guessing flag names. - Always
--jsonwhen the output will be parsed, thenjq; the default table drops columns and truncates values.--text --no-headers --format 'id,label,tags'when a few columns are enough. - Filter server-side right in
list:--tags,--region,--label,--id. That is the project lens on the account:linode-cli linodes list --tags cme. - Default region/type/image already live in
~/.config/linode-cli, so they need not be repeated — but tags have no default and must always be passed. - Every command prints one API-version-mismatch warning. That is stderr noise;
--suppress-warningswhen scripting.
lingate commands
| Command | When |
|---|---|
lingate whoami |
Unsure which project/env you are in. Cheap; run it before any write. |
lingate ls [group] |
What does this project own. |
lingate orphans |
Untagged resources exist; this is the adoption shortlist. |
lingate adopt <g> <id> --env E |
Bring an older resource into the project. Dry-run first; only runs for real with --yes. |
lingate own <g> <id> --env E |
Untaggable type (VPC, database, object storage…) — record it in the ledger. |
lingate disown <g> <id> |
Resource deleted or no longer the project's. |
lingate init <tag> --envs a,b,c |
The repo has no .linode/project.json yet. |
lingate doctor |
Something is off. Prints the whole state and how to fix it. |
Common workflows
Create a resource — project tag and env tag travel together, always:
lingate whoami # which project/env am I in
linode-cli linodes create --tags cme --tags staging \
--label cme-web-1 --region sg-sin-2
Work in another environment — say it on the command line, and ask first:
LINODE_ENV=prod linode-cli linodes reboot 95747451
An untagged resource — do not claim it yourself:
lingate orphans # whose it is, nobody knows yet
lingate adopt linodes 95747451 --env prod # dry run: prints what it would do
# ask the user, and only then:
lingate adopt linodes 95747451 --env prod --yes
Create an untaggable type — add --json so the ledger records it:
linode-cli vpcs create --label cme-vpc --region sg-sin-2 --json
# the PostToolUse hook writes the id to .linode/owned.json; commit that file
A refused command: the reason states exactly what to do. If it says the resource belongs to another project, stop and tell the user — that is the answer "no", not an obstacle to get past.
Tag and label conventions
Two tags per resource, declared in .linode/project.json (committed to git):
<project-name> cme, urgentc, gocova
<environment> dev, staging, prod
Labels follow <project>-<role>[-<n>]: cme-web-1, cme-postgres. LKE worker
nodes are created by their cluster (lke580172-…) and carry no tags of their own —
ownership follows the cluster; do not tag them by hand.
Further reading
references/project-setup.md— set up.linode/project.jsonand adopt older resourcesreferences/guard-rules.md— what the hook refuses, why, and what to do when refusedreferences/secrets.md— root passwords, kubeconfigs and credentials through 1Passwordreferences/cli-cookbook.md—linode-clirecipes for everyday tasks