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 token | Dokploy API |
|---|---|
dokploy:index:Project | project.create / project.one / project.update / project.remove |
dokploy:index:Environment | environment.create / environment.one / environment.update / environment.remove |
dokploy:index:Application | application.create + saveGithubProvider + saveBuildType + saveEnvironment + update / application.one / application.delete |
dokploy:index:Domain | domain.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.typediscriminator 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:
- Pulumi config:
dokploy:apiUrl/dokploy:apiKey(the latter issecret). - 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
- Pulumi-Go-Provider: https://github.com/pulumi/pulumi-go-provider
- Dokploy API overview: https://docs.dokploy.com/docs/api
- Dokploy server routers (source of truth for wire schemas): https://github.com/Dokploy/dokploy/tree/canary/apps/dokploy/server/api/routers