infra/dokploy/README.md

studyflash-dokploy

Pulumi-native resource provider for the self-hosted Dokploy at dokploy.studyflash.ch. Implemented in Go via pulumi-go-provider. Ships a real plugin binary (pulumi-resource-dokploy) and a generated TypeScript SDK at sdk/nodejs/, consumed by sibling stacks under internal/<service>/ via workspace:*.

studyflash-dokploy

Pulumi-native resource provider for the self-hosted Dokploy at dokploy.studyflash.ch. Implemented in Go via pulumi-go-provider. Ships a real plugin binary (pulumi-resource-dokploy) and a generated TypeScript SDK at sdk/nodejs/, consumed by sibling stacks under internal/<service>/ via workspace:*.

This used to be a Pulumi Dynamic Provider in TypeScript. It was rewritten because dynamic providers serialise the JS closure into each resource's state and treat any provider edit as a replace trigger — which, for a provider whose delete calls Dokploy's destructive *.delete endpoints, catastrophically deleted live resources every time a contributor pushed a fix. The native implementation has none of those gaps: read works, pulumi import and pulumi refresh work, and provider edits are a no-op on existing state.

Resources (v0)

Pulumi tokenDokploy API
dokploy:index:Projectproject.create / project.one / project.update / project.remove
dokploy:index:Environmentenvironment.create / environment.one / environment.update / environment.remove
dokploy:index:Applicationapplication.create + saveGithubProvider + saveBuildType + saveEnvironment + update / application.one / application.delete
dokploy:index:Domaindomain.create / domain.one / domain.update / domain.delete

Application is the only multi-step resource: Dokploy splits app configuration across several save* mutations, so create/update sequence the calls so the resource looks atomic to the caller.

Out of scope for v0

  • Databases (Postgres, Mariadb, Mongo, Mysql, Redis, LibSQL)
  • Compose (Docker Compose stacks)
  • Mounts, Redirects, Security, Ports, Registry, Schedule, Backup, SSHKey
  • Non-GitHub source providers (Gitlab/Bitbucket/Gitea/CustomGit/DockerImage) — the schema's source.type discriminator leaves room.

Build / regenerate

The SDK is checked in (TS sources only — bin/, node_modules/, and the provider binary are gitignored). Regenerate after any change to a Go file:

make sdk            # build binary, dump schema, regen + build TS SDK
make install-plugin # symlink the binary into ~/.pulumi/plugins

The Makefile patches the gen-sdk output to add main / types / files fields and to copy package.json into bin/ so module resolution works from a Pulumi runtime that requires the compiled artifacts.

Consuming from a sibling stack

The SDK exposes itself under the npm name studyflash-dokploy via the generated sdk/nodejs/package.json. pnpm picks it up through the infra/** glob in pnpm-workspace.yaml. In the consumer stack:

{
  "dependencies": {
    "studyflash-dokploy": "workspace:*"
  }
}
import * as dokploy from 'studyflash-dokploy';

const project = new dokploy.Project('customer-support', {
  name: 'Customer Support',
});

const env = new dokploy.Environment('production', {
  projectId: project.projectId,
  name: 'production',
});

const app = new dokploy.Application('chatwoot', {
  environmentId: env.environmentId,
  name: 'chatwoot',
  source: {
    type: 'github',
    owner: 'chatwoot',
    repository: 'chatwoot',
    branch: 'master',
    buildPath: '/',
    githubId: 'gh_provider_id_in_dokploy',
  },
  build: { buildType: 'dockerfile' },
  env: {
    POSTGRES_URL: process.env.CHATWOOT_POSTGRES_URL!,
  },
});

new dokploy.Domain('chatwoot-public', {
  applicationId: app.applicationId,
  host: 'support.studyflash.ch',
  https: true,
  port: 3000,
  certificateType: 'letsencrypt',
});

Adopting existing live resources

Because read works, adoption uses the standard Pulumi mechanism — pass import: '<dokploy-id>' in the resource options on the first pulumi up:

new dokploy.Application(
  'support-bot',
  {
    /* declared shape */
  },
  { import: '4mJhD5fLpfICFC4MYclSG' }
);

Pulumi calls our Read(ctx, id), populates state from live Dokploy state, and from then on the resource is managed normally. Once adoption is confirmed (pulumi preview shows zero diff) the import: line can be removed.

Credentials

The provider reads two values, in priority order:

  1. Pulumi config: dokploy:apiUrl / dokploy:apiKey (the latter is secret).
  2. Env vars: DOKPLOY_API_URL / DOKPLOY_API_KEY (used as defaults).

Consumer stacks set the env vars via infisical run --path=/<stack>/ -- pulumi <action>.

Layout

infra/dokploy/
├── main.go              # provider boot
├── client.go            # Dokploy HTTP client
├── resources.go         # Project / Environment / Application / Domain
├── go.mod / go.sum
├── Makefile             # build / schema / gen-sdk / install-plugin
├── README.md
└── sdk/nodejs/          # generated TS SDK (committed; bin/ + node_modules/ gitignored)

References