Skip to content

feat: add declarative apply and export commands - #60

Open
pavliha wants to merge 7 commits into
Dokploy:mainfrom
pavliha:feat/declarative-apply
Open

pavliha wants to merge 7 commits into
Dokploy:mainfrom
pavliha:feat/declarative-apply

Conversation

@pavliha

@pavliha pavliha commented Oct 12, 2026 •

Copy link
Copy Markdown

What is this PR about?

Finishes #30 by @nurikk (credited as co-author), rebuilt on the commander/OpenAPI CLI that replaced oclif in April. It implements the plan in Dokploy/dokploy#4008:

dokploy export -p my-project -f dokploy.yaml       # snapshot a project
dokploy apply -f dokploy.yaml --dry-run --verbose  # preview
dokploy apply -f dokploy.yaml                      # create/update, never delete

One project per file, matched by name; create and update only; env vars stay out of the file. See examples/dokploy.yaml and the README section.

Depends on #52. apply needs parameterized GETs, which are broken on main. #52's commit is included here unchanged so this branch builds and runs; it drops out on rebase once #52 merges. This PR's own change is the single feat: commit and does not touch src/client.ts.

Changes from #30

  • Schedules were duplicated on every apply. application.one/compose.one don't return schedules; they're now read via schedule.list.
  • DB credentials are create-only. postgres.update with a new databasePassword stores it without running ALTER USER (that's changePassword), so the dashboard and the database disagree afterwards.
  • DB matching used project.all, which returns database summaries without names, so existing databases were planned as new. Planning now uses project.one.
  • All fields are applied to new mysql/mariadb/mongo/redis services, not only postgres.
  • Updates send only changed fields. Child updates fill required fields (publishMode, permanent, ...) from the server instead of from schema defaults, which used to overwrite values the file never set.
  • A new database's auto-created data mount is adopted instead of getting a second mount on the same path.
  • Unknown keys are rejected. A missing create-time field (e.g. databasePassword for a new DB) fails before any change is made.

Three additions go beyond #4008. They're listed on the issue for sign-off, and I'm happy to drop any of them:

  • gitProvider: <name> resolves to githubId/gitlabId/..., so GitHub-sourced apps can deploy.
  • export (listed as out of scope in #4008, but part of feat: add declarative apply & export commands #30) never writes passwords, and --verbose masks them.
  • DB credentials being create-only, as above.

Testing

  • pnpm build && pnpm test: 52 tests, covering 99% of lines and 91% of branches in src/lib/apply and the two commands. They mock the API at client.ts and assert exact call sequences, including the payload of every create endpoint (project, environment, all seven service types, all six child types), update merging, credential handling, failure isolation, exit codes and error messages.
  • Dokploy v0.29.1 (a real server):
    • Exported 17 real projects; apply --dry-run of each export reports everything unchanged.
    • Throwaway projects covering every service and child type: create → re-plan (all unchanged) → update every resource type → re-plan → export → re-plan, then deleted.
    • A GitHub-sourced app created by apply (via gitProvider) deployed successfully.
  • Dokploy v0.30.8 (fresh install in a VM): the same create/update/export cycle, plus:
    • serverName resolved against a registered remote server, and exported back by name.
    • Resuming after a run that failed partway (the API key hit better-auth's default rate limit mid-run) completes cleanly.
    • The created app (2 replicas) and postgres deploy and run; postgres has exactly one data mount, the auto-created one adopted from the file.
  • Not covered: GitLab/Gitea/Bitbucket providers live (only the id mapping is unit-tested).

Screenshots

apply --dry-run for a new project:

Applying dokploy.yaml to https://dokploy.example.com
Project "cli-apply-test"                          [create]
  Environment "production"                        [create]
    Application "web"                             [create]
      Domain "cli-apply-test.example.com"         [create]
      Port "18080:tcp"                            [create]
      Mount "/data"                               [create]
      Redirect "^/old$"                           [create]
      Security "admin"                            [create]
      Schedule "noop"                             [create]
    Compose "stack"                               [create]
      Domain "cli-apply-test-compose.example.com:hello"[create]
    Postgres "db"                                 [create]
      Mount "/var/lib/postgresql/data"            [create]
  Environment "staging"                           [create]
    Redis "cache"                                 [create]
(dry-run) No changes applied.

Issues related

Closes #30. Implements Dokploy/dokploy#4008. Depends on #52.

@luixo

luixo commented Oct 12, 2026

Copy link
Copy Markdown

How are envs in docker compose / env / project treated?
It would be great if I could include those as well (I use secret manager extensively hence none of my vars are actually sensitive)

@pavliha

pavliha commented Oct 14, 2026 •

Copy link
Copy Markdown
Author

Hi @luixo, I added it. You can now set env on the project, on each environment and on each service (applications also take buildArgs and buildSecrets):

env: |
  LOG_LEVEL=info
environments:
  - name: production
    env: |
      API_URL=https://api.example.com
    applications:
      - name: web-api
        env: |
          LOG_LEVEL=${{project.LOG_LEVEL}}
          PUBLIC_URL=${{environment.API_URL}}

export leaves env out by default. Add --include-env to include it.

@luixo

luixo commented Oct 14, 2026

Copy link
Copy Markdown

Awesome, thanks!
I'll try out your branch on my project and will get back with feedback

@luixo

luixo commented Oct 14, 2026

Copy link
Copy Markdown

So the feedback for my case - backing up my projects and restoring that backup on a new machine:

  • Why do we only export project and not other entities? I use secret manager, S3 storage, github account - and there are dozens of other services for which settings should be saved.
  • Why do we export only single project and not all of them at once?
  • I found out empirically that -p means project name and not project id. I suggest using projectId as that removes ambiguity and helps remove escaping problem (for project names that have whitespace, special chars etc).
  • Only added or updated options from config are applied. Why not remove the missing ones? That's a sane default, but let's have an options like --force or --override.
  • Can we be sure that our apps/composes will be in the same order if we roll them out from a snapshot? Can it be they will roll out in a different order and it will be a mismatch?

gooyoung and others added 7 commits October 15, 2026 19:00
`dokploy apply -f dokploy.yaml` creates or updates a project from a YAML
file, matching projects, environments and services by name. `--dry-run`
prints the plan and `--verbose` shows field-level changes.
`dokploy export -p <project>` writes an existing project as a
dokploy.yaml that applies back as unchanged.

Ported from Dokploy#30 onto the commander CLI, with these changes:
- read schedules through schedule.list, since application.one and
  compose.one do not return them (re-applying duplicated every schedule)
- treat database credentials as create-only: update would overwrite the
  stored password without running ALTER USER, so the dashboard and the
  database would disagree
- apply every field to new mysql, mariadb, mongo and redis services, not
  only postgres
- send only changed fields on update; child updates fill required fields
  from the server instead of schema defaults that overwrote values the
  file never set
- after creating a database, diff its mounts against the auto-created
  data volume instead of adding a second mount on the same path
- resolve `gitProvider` by name so GitHub-sourced apps can deploy
- reject unknown keys, and fail before any change when a new resource
  is missing fields the server needs to create it
- match services through project.one; project.all returns database
  summaries without names
- export only declarative fields and never write passwords, and mask
  passwords in --verbose diffs

Implements Dokploy/dokploy#4008.

Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
- env on project, environments and every service; buildArgs and
  buildSecrets on applications, all sent through the existing update path
- export omits them unless --include-env; --verbose masks their values
- environments matched on create (the auto-created production) now get
  their description and env applied instead of being skipped
- planning fails when serverName would move an existing service, instead
  of silently ignoring it
- export writes the file with mode 0600 so --include-env secrets stay private
- includeEnv is a required export option instead of a silent default
- tests for env updates on existing resources and for keeping a service
  on the server it already runs on
- a file can hold several YAML documents; an optional kind: Organization
  document manages SSH keys, servers, S3 destinations, registries,
  certificates, notifications and secret managers by name, applied before
  projects (secret managers after, so they can be assigned to them)
- apply --prune deletes environments, services and their domains, ports,
  mounts, redirects, basic auth and schedules missing from the file; the
  default environment is emptied, database data mounts and compose volumes
  are kept, organization settings are never pruned
- export --all writes the organization plus every project, sorted by name;
  -p accepts a project id or name; --include-secrets adds passwords, keys
  and tokens on top of --include-env
- serverName resolves at execution time so a server declared in the same
  file can be used by the services that follow
@pavliha
pavliha force-pushed the feat/declarative-apply branch from 0692312 to 838ee23 Compare October 15, 2026 15:47
@pavliha

pavliha commented Oct 16, 2026 •

Copy link
Copy Markdown
Author

So the feedback for my case - backing up my projects and restoring that backup on a new machine:

  • Why do we only export project and not other entities? I use secret manager, S3 storage, github account - and there are dozens of other services for which settings should be saved.
  • Why do we export only single project and not all of them at once?
  • I found out empirically that -p means project name and not project id. I suggest using projectId as that removes ambiguity and helps remove escaping problem (for project names that have whitespace, special chars etc).
  • Only added or updated options from config are applied. Why not remove the missing ones? That's a sane default, but let's have an options like --force or --override.
  • Can we be sure that our apps/composes will be in the same order if we roll them out from a snapshot? Can it be they will roll out in a different order and it will be a mismatch?

hi I updated based on your feedback

@luixo

luixo commented Oct 20, 2026

Copy link
Copy Markdown

Thanks, I appreciate it a lot, that's great fit for my usecase!

  • Database credentials (databaseName, databaseUser, databasePassword, databaseRootPassword) and SSH key pairs are only used when creating them; change them later from the dashboard.

Found this in the README. What does that mean? If I backed them up and reapplied later, why would I need to change them from the dashboard? I miss some context here.

  • A new server still has to be set up from the dashboard after apply registers it.

What does that mean?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants