The dya CLI reads configuration from four places: a project config, a per-user global config, key files in your project, and command-line flags. This page documents each one and the order they are resolved in.
The .dya/ folder
Everything the CLI owns lives in one hidden folder at your project's web root:
your-project/
package.json
src/
.dya/
config.json # project config — commit this
dya-signing-private.pem # mode 0600 — never commit
dya-signing-public.pem
dya-encryption-public.pem
dya-encryption-private.pem # mode 0600 — never commit
One folder rather than five loose files in your project root. The config inside it is meant to be committed; everything else is not, which is exactly what the .gitignore block below expresses.
Where the web root is
The CLI resolves the project by walking up from the directory you run it in. The web root is the directory holding package.json and src/ — it anchors .dya/ and holds your app's entry point. A directory that already contains .dya/config.json wins over structural guessing, because a project that has been set up has already answered the question.
The Capacitor root is the first of these that contains a capacitor.config.json, .ts, or .js, relative to the web root:
- the web root itself
src-capacitor/ios/App/android/app/
In a Quasar project these are different directories: the web app (package.json, src/, quasar.config.ts) sits at the project root, and the Capacitor project (capacitor.config.json, android/, ios/, a second package.json) sits in src-capacitor/. Both are resolved, and commands work the same whichever of the two you run them from — running from inside src-capacitor/ resolves up to the web root rather than treating the Capacitor directory as the project.
A project config found anywhere other than the web root is a leftover from a command run in the wrong directory. The web root's copy wins and the stray is reported as a warning, because two configs that drift is how one command deploys to a different channel than another.
Project config
dya setup (and its alias dya init) writes .dya/config.json at the web root. It is JSON, it is meant to be committed, and it links the project to an app on the server.
{
"appId": "com.example.shop",
"channel": "production",
"path": "dist/spa"
}
| Field | Type | Default | Description |
|---|---|---|---|
appId |
string | "" |
The app identifier registered on the server. Must start with a letter and contain only letters, numbers, ., _, or -, up to 255 characters. |
channel |
string | production |
Channel used by dya deploy and dya rollback when --channel is not passed. |
path |
string | null | null |
Directory of built web assets, stored as a relative forward-slash path so the file works on any machine and OS. |
capacitorRoot |
string | — | Where the Capacitor project is, relative to the web root, in forward slashes. Written by dya setup. Later commands use it instead of re-detecting, so they cannot disagree about where the native project is. Ignored if the directory no longer exists. |
The file is written with two-space indentation and a trailing newline. Missing or malformed JSON is treated as "no project config": dya deploy and dya rollback then exit 1 with No project config found. Run `dya setup --app-id <appId>` first., and the dya channels commands fail with that same message.
When path is null, dya setup and dya deploy auto-detect it, accepting the first directory that contains an index.html:
- The
webDirdeclared incapacitor.config.*, resolved against the Capacitor root <capacitor root>/wwwdist/spa,dist/pwa,dist,www,build,publicunder the web root
webDir leads because it is the only authoritative answer: it is the directory npx cap sync copies into the native app, so whatever is there is what ships. In a split layout that is usually the only place the build exists — Quasar in Capacitor mode writes to src-capacitor/www, which no amount of searching the web root will find.
The index.html requirement applies to every candidate, so a webDir that has not been built yet falls through to a real build instead of winning while empty. Nested outputs are checked before their parents so a dist folder holding only subdirectories never shadows the real assets.
A capacitor.config.ts or .js is code and is not evaluated — a webDir string literal is read out of it, and a computed value simply yields nothing and falls through to the conventions.
Which commands read it:
| Command | Uses |
|---|---|
dya deploy |
appId, channel, path |
dya rollback |
appId, channel |
dya channels list / create / delete |
appId |
dya keys generate / upload |
appId, unless --app-id overrides it |
The legacy location
Before .dya/ existed, the project config was a file named .deployyourapp at the project root. It is still read, so a project set up by an older CLI keeps working with no migration and no flag day. .dya/config.json is preferred when both exist.
Nothing writes .deployyourapp any more. The first command that updates your config moves the project to .dya/config.json, and dya setup, dya keys generate, and dya deploy offer to move the old files into .dya/ for you:
⚠ Found DeployYourApp files outside .dya/:
dya-signing-private.pem
dya-encryption-private.pem
.deployyourapp
? Move them into .dya/ ? (Y/n):
Each moved file is reported as ✓ Moved <from> → <to>; anything that could not be moved — most often because a file of that name already exists in .dya/ — is reported as ⚠ Left <path> where it is: <reason> and left untouched.
The move is copy, read back and byte-compare, then delete — a failure at any step leaves the original untouched, because a signing key cannot be regenerated once apps have shipped with its public half. Declining is fine; the old locations keep resolving. In a non-interactive shell the prompt is skipped entirely and nothing moves, so CI output is unchanged.
When any of the files being moved is a private key, the .gitignore block is written first and the migration is abandoned if that write fails — see below.
.gitignore
dya keys generate writes this block to the .gitignore at your web root, creating the file if you have none:
# DeployYourApp — keys and local state. Never commit these.
.dya/*
!.dya/config.json
dya setup and dya deploy write the same block, whenever you accept their offer to move a stray private key into .dya/. The rules go in before the key moves, so an interrupted migration can only ever leave the key ignored, never exposed. If the .gitignore cannot be written at that point the migration is abandoned — nothing moves, and the CLI prints Could not update .gitignore: <reason> followed by Nothing was moved. and the lines to add by hand. Leaving the key where it is keeps whatever protection it already had, because a rule naming the old filename stops matching the moment the file is renamed into .dya/.
Commit this block along with .dya/config.json. CI reads the committed config for the app ID and channel.
It must be .dya/*, not .dya/. Git cannot re-include a file whose parent directory is excluded, so .dya/ followed by !.dya/config.json would leave the config ignored — silently, since the file still exists on your machine — and CI would lose the app ID and channel with no error. Excluding the directory's contents keeps the negation effective.
Those two lines are written as a unit, in that order, or not at all: the last matching rule wins, so appending only the missing line could put an exclusion after its own negation. That all-or-nothing rule applies to this block specifically, because it contains a negation. Re-running the command when both lines are present changes nothing (it reports Already in .gitignore: .dya/*, !.dya/config.json instead).
A .dya/ rule is repaired for you
You do not have to hand-fix a .gitignore that already excludes the directory itself. Before writing the block, the CLI looks for a line that would make the negation unreachable — .dya, .dya/, or /.dya/, with or without a leading slash — removes it, and reports the swap:
⚠ .gitignore had `.dya/`, which hid .dya/config.json from git as well —
⚠ replaced with `.dya/*`, which ignores the same files but lets the project config be committed.
The replacement ignores exactly the same files. Only that one line is dropped; every other byte of your .gitignore — including CRLF line endings and trailing whitespace you put there — is left as it was. The repair runs on the commands that write the block: dya keys generate, and dya setup or dya deploy when you accept the stray-key migration. A pipeline that only runs dya deploy never reaches it, so fix the file on a developer machine and commit the result.
If a private key is still sitting outside .dya/ — at the web root, at the Capacitor root, or under a legacy filename — its path is appended as a second group, so declining the migration leaves you protected either way. That group has no negation in it, so it is topped up one entry at a time: a path already present is skipped rather than re-appended, and re-running never duplicates a line. Only dya-signing-private.pem and dya-encryption-private.pem (and the legacy .deployyourapp-private.pem) get this treatment; the public keys are not secrets. If the .gitignore cannot be written at all, dya keys generate warns and prints the lines to add by hand.
The rule is checked against real git
The lines being present is not the same thing as git honouring them, and the reason cannot always be found in the file the CLI just wrote: a .dya/ rule in a parent directory's .gitignore, a line in this clone's .git/info/exclude, or one in your global excludes file all leave !.dya/config.json inert. So the outcome is checked rather than predicted. Once the block is written, the CLI asks git check-ignore whether .dya/config.json is still ignored, and reports anything that is — naming the file, the line and the pattern:
✗ git still ignores .dya/config.json, so it will not be committed and CI will have no appId or channel.
✗ The rule beating it is `.dya/` — /home/ada/work/.gitignore:12 (a .gitignore in a directory above this project).
⚠ That file belongs to a directory above this project and was not touched. Narrow the rule there to `.dya/*`, or point it at the directory it was meant for.
⚠ Check it with: git check-ignore -v .dya/config.json
Only this project's own .gitignore is a file the CLI will edit. Every other source is named and left exactly as it is, with the advice that fits where the rule lives:
| Where the rule is | Named as | What happens |
|---|---|---|
this project's .gitignore |
this project's .gitignore |
Delete or narrow that line: `.dya/*` ignores exactly the same files without hiding the config. |
a .gitignore above the project |
a .gitignore in a directory above this project |
Not touched. Narrow the rule there to .dya/*, or point it at the directory it was meant for. |
this clone's .git/info/exclude |
this clone's .git/info/exclude |
Not touched. Remove the line from it. |
your core.excludesFile |
your global git excludes file (core.excludesFile) |
Not touched — it is yours and applies to every repository. Remove or narrow the line in it. |
| a source git named but that could not be placed | a git exclude source git did not name |
Nothing here can fix it; the printed git check-ignore -v command names the file. |
The check degrades silently rather than failing the write: no git on PATH, a directory that is not a repository, or a git call that takes longer than five seconds all mean "no verdict", and nothing is reported. It runs on the same commands that write the block — dya keys generate, and dya setup or dya deploy when you accept the stray-key migration — including on a run where every line was already there.
Only the block's negated path is checked, which is .dya/config.json. The private-key group has no negation in it, so there is nothing to verify there — an ignored private key is the outcome those lines are for.
Global config
Credentials and the active organization live in a per-user JSON file outside your project, managed by the conf package. It is created with 0600 permissions because it holds a session token or API key. You normally never edit it by hand.
| Field | Type | Default | Set by |
|---|---|---|---|
token |
string | — | dya login (browser flow). A 30-day session token. Deleted by dya login --api-key and dya logout. |
apiKey |
string | — | dya login --api-key. Deleted by the browser login and dya logout. |
activeOrgId |
string | — | dya orgs switch, dya setup, or auto-selected when your account has exactly one organization. |
activeOrgName |
string | — | Display name for activeOrgId, cached so commands can show the organization name without an extra request. Always written at the same time as activeOrgId, so the two cannot drift apart. |
serverUrl |
string | https://api.deployyour.app |
dya login --server <url> or dya setup --server <url>. Trailing slashes are stripped before storing. |
File location
The file is named config.json and lives in the per-user config directory for dya-nodejs:
| OS | Path |
|---|---|
| Windows | %APPDATA%\dya-nodejs\Config\config.json |
| macOS | ~/Library/Preferences/dya-nodejs/config.json |
| Linux | $XDG_CONFIG_HOME/dya-nodejs/config.json, or ~/.config/dya-nodejs/config.json when XDG_CONFIG_HOME is unset |
Because the config is per-user and not per-project, one login covers every project on the machine, and one dya orgs switch changes the organization for all of them.
Authentication headers
token and apiKey are never both used. An API key is sent as X-API-Key. A session token is sent as both an Authorization: Bearer header and a dya.session_token cookie. When both values are somehow present, the API key wins.
Key files
Signing is driven entirely by the presence of dya-signing-private.pem in your project. There is no flag to turn it on. Bundles are never encrypted — dya deploy uploads a plain, signed ZIP; see Bundle asset policy.
| File | Read by | Effect |
|---|---|---|
.dya/dya-signing-private.pem |
dya deploy |
Signs the uploaded bytes with RSA-4096/SHA-256. Absent means the deploy is refused unless --allow-unsigned is passed. |
.dya/dya-signing-public.pem |
dya keys upload |
Uploaded to the server so it can verify signatures. |
.dya/dya-encryption-public.pem |
dya keys upload |
Uploaded to the server. Not read by dya deploy — bundles are never encrypted. |
.dya/dya-encryption-private.pem |
dya setup |
Offered for embedding in your app binary. Not read by dya deploy. |
dya keys generate writes all four into .dya/, with 0600 on the two private
keys (a no-op on Windows, where file permissions work differently). It also
writes the .gitignore block above.
Where keys are looked up
New keys are always written to .dya/. Keys that are already somewhere else keep working — each one is searched for in this order, and the first hit wins:
.dya/<name>at the web root — the current location<name>at the web root — where older CLI versions wrote<name>at the Capacitor root — where a run from the wrong directory landed- the legacy filenames, in those same three directories
Two filenames predate the dya- prefix and are still honoured at step 4: .deployyourapp-private.pem for the signing private key and .deployyourapp-encrypt.pem for the encryption public key. They are read, never written.
There is no flag day. A project generated by any earlier CLI keeps deploying untouched, and the migration prompt is always optional.
Looking after them
The .gitignore block stops the keys reaching a remote; it does not back them
up. Keep copies in a password manager or secrets vault. Losing
dya-signing-private.pem means you cannot ship another update that installed
apps will accept — they verify against the public key already compiled into
them. Leaking it means anyone can sign a bundle your users' apps will install
and run.
One signing keypair per app. dya keys generate refuses to run when a signing key already exists anywhere in the project, and if it finds two distinct keypairs it exits 1 without writing anything, showing each one's fingerprint and marking the one that matches the key embedded in capacitor.config.json so you can delete the other. dya setup runs the same check first thing, before it writes the project config, installs the plugin or runs cap sync, and stops with Setup stopped before writing anything. — see dya setup. It also refuses when dya-signing-public.pem is not the public half of dya-signing-private.pem. And it will not quietly overwrite a publicKey already embedded in capacitor.config.json with a different one: replacing a key that may already have shipped takes typing replace the signing key, and a bare Enter keeps the embedded key and stops setup. See Code signing for why picking the wrong one is unrecoverable.
Running dya deploy with no keys present stops and offers to generate them. In
CI there is no terminal to prompt, so it declines rather than minting a key that
would vanish with the build agent. With still no signing key, the deploy then
refuses to upload an unsigned bundle and exits 1, unless --allow-unsigned
was passed.
Environment variables
The CLI reads no environment variable for authentication. There is no DYA_API_KEY or equivalent — an API key must be passed explicitly to dya login --api-key, typically from a shell variable your CI provides.
These variables do affect the CLI:
| Variable | Type | Default | Effect |
|---|---|---|---|
API_URL |
string | https://api.deployyour.app |
Fallback API server base URL. Only used when serverUrl is absent from the global config, so a previous dya login --server overrides it. |
PRODUCT_DOMAIN |
string | — | Domain the default API_URL is derived from, as https://api.<domain>. Ignored when API_URL is set. |
PRODUCT_NAME |
string | DeployYourApp |
Product name in the CLI's help text. |
XDG_CONFIG_HOME |
string | ~/.config |
Linux only. Parent directory of the global config. |
APPDATA |
string | %USERPROFILE%\AppData\Roaming |
Windows only. Parent directory of the global config. |
FORCE_COLOR |
0–3, true, false |
— | Forces colored output on or off. Useful when a CI log viewer renders ANSI colors. |
Precedence
For a value used by dya deploy, highest priority first:
- Command-line flag —
--channel beta,--path dist,--version 1.4.0. - Project config —
channelandpathfrom.dya/config.json. - Auto-detection or built-in default — the assets directory scan,
productionfor the channel,100for the rollout,capacitorfor the target, and the web-rootpackage.jsonversion plus a UTC build stamp for the version.
The version is the one default that is never guessed at: an unreadable or missing package.json version fails the deploy instead of falling back to something generated. See Bundle versions come from package.json.
The channel specifically resolves as flag > channel in .dya/config.json > production, and dya deploy and dya rollback share that one resolver. A bare dya rollback therefore targets the same channel a bare dya deploy shipped to. An empty or whitespace-only --channel is treated as if the flag were absent.
For the server URL, highest priority first:
--server <url>on the currentdya loginordya setupinvocation, which also persists it.serverUrlin the global config, from an earlier--server.- The
API_URLenvironment variable. - The built-in default,
https://api.deployyour.app.
Note the ordering consequence: once --server has been used on a machine, setting API_URL has no effect. Run dya login --server <url> again to change it.
For the organization, highest priority first:
activeOrgIdin the global config.- Auto-selection, when the account belongs to exactly one organization. The result is written back to
activeOrgId. - Nothing — commands that need an organization exit
1and ask you to rundya orgs switch.
Pointing at another environment
Almost nobody needs this. The CLI targets https://api.deployyour.app, the
hosted service at deployyour.app, and that is the only
environment most people ever use. --server exists for working against a
different DeployYourApp API — a local server during development, or a staging
environment:
dya login --server http://localhost:3000
DeployYourApp is a hosted service; there is no self-hosted distribution of the server to point this at.
The URL persists in the global config and applies to every later command, including dya setup. When the CLI is pointed at a non-default server, dya setup also writes updateUrl and statsUrl into the generated Capacitor plugin config so devices check the same server.
Related pages
- Command reference — every flag and its default.
- CI/CD integration — configuring the CLI in a pipeline.