Point at dokploy-deploy-action for rollouts
This commit is contained in:
25
README.md
25
README.md
@@ -49,6 +49,7 @@ new dokploy.Domain("api", {
|
|||||||
- [Installing](#installing)
|
- [Installing](#installing)
|
||||||
- [Configuration](#configuration)
|
- [Configuration](#configuration)
|
||||||
- [Resources and data sources](#resources-and-data-sources)
|
- [Resources and data sources](#resources-and-data-sources)
|
||||||
|
- [Deploying from CI](#deploying-from-ci)
|
||||||
- [Differences from the Terraform provider](#differences-from-the-terraform-provider)
|
- [Differences from the Terraform provider](#differences-from-the-terraform-provider)
|
||||||
- [Examples](#examples)
|
- [Examples](#examples)
|
||||||
- [Development](#development)
|
- [Development](#development)
|
||||||
@@ -123,6 +124,30 @@ pulumi config set --secret dokploy:apiKey "$DOKPLOY_API_KEY"
|
|||||||
Data sources: `getProject`, `getProjects`, `getEnvironment`, `getApplication`,
|
Data sources: `getProject`, `getProjects`, `getEnvironment`, `getApplication`,
|
||||||
`getServers`.
|
`getServers`.
|
||||||
|
|
||||||
|
## Deploying from CI
|
||||||
|
|
||||||
|
Like the Terraform provider, this one manages *configuration*, not *rollouts*.
|
||||||
|
Creating an `Application` writes its definition; it does not build or start
|
||||||
|
anything.
|
||||||
|
|
||||||
|
[`dokploy-deploy-action`](https://github.com/maxvojtkov/dokploy-deploy-action)
|
||||||
|
covers the other half on GitHub Actions and Gitea Actions: it triggers a
|
||||||
|
deployment, waits for the build, and fails the job when the deployment fails.
|
||||||
|
Export the application id from your stack and hand it over.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export const apiApplicationId = api.id;
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- uses: maxvojtkov/dokploy-deploy-action@v1
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.DOKPLOY_HOST }}
|
||||||
|
api-key: ${{ secrets.DOKPLOY_API_KEY }}
|
||||||
|
application-id: ${{ steps.stack.outputs.apiApplicationId }}
|
||||||
|
docker-image: ghcr.io/acme/api:${{ github.sha }}
|
||||||
|
```
|
||||||
|
|
||||||
## Differences from the Terraform provider
|
## Differences from the Terraform provider
|
||||||
|
|
||||||
Everything is a mechanical translation of the Terraform provider, with three
|
Everything is a mechanical translation of the Terraform provider, with three
|
||||||
|
|||||||
@@ -18,46 +18,52 @@ const adminPassword = config.requireSecret("adminPassword");
|
|||||||
// ---------------------------------------------------------------- structure
|
// ---------------------------------------------------------------- structure
|
||||||
|
|
||||||
const shop = new dokploy.Project("shop", {
|
const shop = new dokploy.Project("shop", {
|
||||||
name: "shop",
|
name: "shop",
|
||||||
description: "Storefront and its backing services",
|
description: "Storefront and its backing services",
|
||||||
// Shared by every service in the project.
|
// Shared by every service in the project.
|
||||||
env: "TZ=Europe/Bucharest",
|
env: "TZ=Europe/Bucharest",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Dokploy creates a default "production" environment with the project; add a
|
// Dokploy creates a default "production" environment with the project; add a
|
||||||
// second one for staging.
|
// second one for staging.
|
||||||
const staging = new dokploy.Environment("staging", {
|
const staging = new dokploy.Environment("staging", {
|
||||||
name: "staging",
|
name: "staging",
|
||||||
description: "Pre-production",
|
description: "Pre-production",
|
||||||
projectId: shop.id,
|
projectId: shop.id,
|
||||||
env: "LOG_LEVEL=debug",
|
env: "LOG_LEVEL=debug",
|
||||||
});
|
});
|
||||||
|
|
||||||
// ------------------------------------------------------------------ secrets
|
// ------------------------------------------------------------------ secrets
|
||||||
|
|
||||||
const postgresPassword = new random.RandomPassword("postgres", { length: 32, special: false });
|
const postgresPassword = new random.RandomPassword("postgres", {
|
||||||
const redisPassword = new random.RandomPassword("redis", { length: 32, special: false });
|
length: 32,
|
||||||
|
special: false,
|
||||||
|
});
|
||||||
|
const redisPassword = new random.RandomPassword("redis", {
|
||||||
|
length: 32,
|
||||||
|
special: false,
|
||||||
|
});
|
||||||
|
|
||||||
// --------------------------------------------------------------- datastores
|
// --------------------------------------------------------------- datastores
|
||||||
|
|
||||||
const db = new dokploy.Postgres("db", {
|
const db = new dokploy.Postgres("db", {
|
||||||
name: "shop-db",
|
name: "shop-db",
|
||||||
environmentId: shop.defaultEnvironmentId,
|
environmentId: shop.defaultEnvironmentId,
|
||||||
dockerImage: "postgres:16-alpine",
|
dockerImage: "postgres:16-alpine",
|
||||||
databaseName: "shop",
|
databaseName: "shop",
|
||||||
databaseUser: "shop",
|
databaseUser: "shop",
|
||||||
databasePassword: postgresPassword.result,
|
databasePassword: postgresPassword.result,
|
||||||
memoryLimit: "1g",
|
memoryLimit: "1g",
|
||||||
cpuLimit: "1",
|
cpuLimit: "1",
|
||||||
memoryReservation: "256m",
|
memoryReservation: "256m",
|
||||||
});
|
});
|
||||||
|
|
||||||
const cache = new dokploy.Redis("cache", {
|
const cache = new dokploy.Redis("cache", {
|
||||||
name: "shop-cache",
|
name: "shop-cache",
|
||||||
environmentId: shop.defaultEnvironmentId,
|
environmentId: shop.defaultEnvironmentId,
|
||||||
dockerImage: "redis:7-alpine",
|
dockerImage: "redis:7-alpine",
|
||||||
databasePassword: redisPassword.result,
|
databasePassword: redisPassword.result,
|
||||||
memoryLimit: "256m",
|
memoryLimit: "256m",
|
||||||
});
|
});
|
||||||
|
|
||||||
// ----------------------------------------------------------- the web service
|
// ----------------------------------------------------------- the web service
|
||||||
@@ -65,63 +71,65 @@ const cache = new dokploy.Redis("cache", {
|
|||||||
// Dokploy resolves service names on the shared Docker network, so the database
|
// Dokploy resolves service names on the shared Docker network, so the database
|
||||||
// is reachable by its generated appName.
|
// is reachable by its generated appName.
|
||||||
const storefrontEnv = pulumi
|
const storefrontEnv = pulumi
|
||||||
.all([db.appName, postgresPassword.result, cache.appName, redisPassword.result])
|
.all([db.appName, postgresPassword.result, cache.appName, redisPassword.result])
|
||||||
.apply(([dbHost, dbPass, cacheHost, cachePass]) => [
|
.apply(([dbHost, dbPass, cacheHost, cachePass]) =>
|
||||||
`DATABASE_URL=postgresql://shop:${dbPass}@${dbHost}:5432/shop`,
|
[
|
||||||
`REDIS_URL=redis://:${cachePass}@${cacheHost}:6379`,
|
`DATABASE_URL=postgresql://shop:${dbPass}@${dbHost}:5432/shop`,
|
||||||
"NODE_ENV=production",
|
`REDIS_URL=redis://:${cachePass}@${cacheHost}:6379`,
|
||||||
].join("\n"));
|
"NODE_ENV=production",
|
||||||
|
].join("\n"),
|
||||||
|
);
|
||||||
|
|
||||||
const storefront = new WebService("storefront", {
|
const storefront = new WebService("storefront", {
|
||||||
environmentId: shop.defaultEnvironmentId,
|
environmentId: shop.defaultEnvironmentId,
|
||||||
description: "Public storefront",
|
description: "Public storefront",
|
||||||
github: {
|
github: {
|
||||||
githubId: githubProviderId,
|
githubId: githubProviderId,
|
||||||
owner: "acme",
|
owner: "acme",
|
||||||
repository: "storefront",
|
repository: "storefront",
|
||||||
branch: "main",
|
branch: "main",
|
||||||
},
|
},
|
||||||
env: storefrontEnv,
|
env: storefrontEnv,
|
||||||
replicas: 2,
|
replicas: 2,
|
||||||
memoryLimit: "1g",
|
memoryLimit: "1g",
|
||||||
cpuLimit: "1",
|
cpuLimit: "1",
|
||||||
domains: [{ host: "shop.example.com", port: 3000 }],
|
domains: [{ host: "shop.example.com", port: 3000 }],
|
||||||
});
|
});
|
||||||
|
|
||||||
new dokploy.Redirect("storefront-www", {
|
new dokploy.Redirect("storefront-www", {
|
||||||
applicationId: storefront.application.id,
|
applicationId: storefront.application.id,
|
||||||
regex: String.raw`^https://www\.shop\.example\.com/(.*)`,
|
regex: String.raw`^https://www\.shop\.example\.com/(.*)`,
|
||||||
replacement: "https://shop.example.com/${1}",
|
replacement: "https://shop.example.com/${1}",
|
||||||
permanent: true,
|
permanent: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
new dokploy.Mount("storefront-uploads", {
|
new dokploy.Mount("storefront-uploads", {
|
||||||
serviceId: storefront.application.id,
|
serviceId: storefront.application.id,
|
||||||
serviceType: "application",
|
serviceType: "application",
|
||||||
type: "volume",
|
type: "volume",
|
||||||
mountPath: "/app/uploads",
|
mountPath: "/app/uploads",
|
||||||
volumeName: "storefront-uploads",
|
volumeName: "storefront-uploads",
|
||||||
});
|
});
|
||||||
|
|
||||||
// ------------------------------- an internal tool behind basic auth (staging)
|
// ------------------------------- an internal tool behind basic auth (staging)
|
||||||
|
|
||||||
new WebService("admin", {
|
new WebService("admin", {
|
||||||
environmentId: staging.id,
|
environmentId: staging.id,
|
||||||
dockerImage: "traefik/whoami:latest",
|
dockerImage: "traefik/whoami:latest",
|
||||||
domains: [{ host: "admin.staging.example.com", port: 80 }],
|
domains: [{ host: "admin.staging.example.com", port: 80 }],
|
||||||
basicAuth: { username: "ops", password: adminPassword },
|
basicAuth: { username: "ops", password: adminPassword },
|
||||||
});
|
});
|
||||||
|
|
||||||
// -------------------------------------------------------------- compose stack
|
// -------------------------------------------------------------- compose stack
|
||||||
|
|
||||||
const observability = new dokploy.Compose("observability", {
|
const observability = new dokploy.Compose("observability", {
|
||||||
name: "observability",
|
name: "observability",
|
||||||
environmentId: shop.defaultEnvironmentId,
|
environmentId: shop.defaultEnvironmentId,
|
||||||
composeType: "docker-compose",
|
composeType: "docker-compose",
|
||||||
sourceType: "raw",
|
sourceType: "raw",
|
||||||
// Preserve volumes if this stack is ever destroyed.
|
// Preserve volumes if this stack is ever destroyed.
|
||||||
deleteVolumes: false,
|
deleteVolumes: false,
|
||||||
composeFile: `services:
|
composeFile: `services:
|
||||||
uptime:
|
uptime:
|
||||||
image: louislam/uptime-kuma:1
|
image: louislam/uptime-kuma:1
|
||||||
volumes:
|
volumes:
|
||||||
@@ -134,13 +142,13 @@ volumes:
|
|||||||
});
|
});
|
||||||
|
|
||||||
new dokploy.Domain("observability", {
|
new dokploy.Domain("observability", {
|
||||||
composeId: observability.id,
|
composeId: observability.id,
|
||||||
domainType: "compose",
|
domainType: "compose",
|
||||||
serviceName: "uptime",
|
serviceName: "uptime",
|
||||||
host: "status.example.com",
|
host: "status.example.com",
|
||||||
port: 3001,
|
port: 3001,
|
||||||
https: true,
|
https: true,
|
||||||
certificateType: "letsencrypt",
|
certificateType: "letsencrypt",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const projectId = shop.id;
|
export const projectId = shop.id;
|
||||||
|
|||||||
1850
examples/typescript/pnpm-lock.yaml
generated
Normal file
1850
examples/typescript/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,33 +2,36 @@ import * as pulumi from "@pulumi/pulumi";
|
|||||||
import * as dokploy from "@maxvojtkov/pulumi-dokploy";
|
import * as dokploy from "@maxvojtkov/pulumi-dokploy";
|
||||||
|
|
||||||
export interface DomainSpec {
|
export interface DomainSpec {
|
||||||
host: string;
|
host: string;
|
||||||
port: number;
|
port: number;
|
||||||
https?: boolean;
|
https?: boolean;
|
||||||
path?: string;
|
path?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WebServiceArgs {
|
export interface WebServiceArgs {
|
||||||
environmentId: pulumi.Input<string>;
|
environmentId: pulumi.Input<string>;
|
||||||
description?: pulumi.Input<string>;
|
description?: pulumi.Input<string>;
|
||||||
|
|
||||||
/** Either a prebuilt image or a GitHub repository, not both. */
|
/** Either a prebuilt image or a GitHub repository, not both. */
|
||||||
dockerImage?: pulumi.Input<string>;
|
dockerImage?: pulumi.Input<string>;
|
||||||
github?: {
|
github?: {
|
||||||
githubId: pulumi.Input<string>;
|
githubId: pulumi.Input<string>;
|
||||||
owner: pulumi.Input<string>;
|
owner: pulumi.Input<string>;
|
||||||
repository: pulumi.Input<string>;
|
repository: pulumi.Input<string>;
|
||||||
branch: pulumi.Input<string>;
|
branch: pulumi.Input<string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
env?: pulumi.Input<string>;
|
env?: pulumi.Input<string>;
|
||||||
replicas?: pulumi.Input<number>;
|
replicas?: pulumi.Input<number>;
|
||||||
memoryLimit?: pulumi.Input<string>;
|
memoryLimit?: pulumi.Input<string>;
|
||||||
cpuLimit?: pulumi.Input<string>;
|
cpuLimit?: pulumi.Input<string>;
|
||||||
|
|
||||||
domains?: DomainSpec[];
|
domains?: DomainSpec[];
|
||||||
/** username/password pair to put the whole service behind basic auth. */
|
/** username/password pair to put the whole service behind basic auth. */
|
||||||
basicAuth?: { username: pulumi.Input<string>; password: pulumi.Input<string> };
|
basicAuth?: {
|
||||||
|
username: pulumi.Input<string>;
|
||||||
|
password: pulumi.Input<string>;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,38 +39,53 @@ export interface WebServiceArgs {
|
|||||||
* Pulumi equivalent of the `web-service` Terraform module.
|
* Pulumi equivalent of the `web-service` Terraform module.
|
||||||
*/
|
*/
|
||||||
export class WebService extends pulumi.ComponentResource {
|
export class WebService extends pulumi.ComponentResource {
|
||||||
public readonly application: dokploy.Application;
|
public readonly application: dokploy.Application;
|
||||||
public readonly domains: dokploy.Domain[];
|
public readonly domains: dokploy.Domain[];
|
||||||
|
|
||||||
constructor(name: string, args: WebServiceArgs, opts?: pulumi.ComponentResourceOptions) {
|
constructor(name: string, args: WebServiceArgs, opts?: pulumi.ComponentResourceOptions) {
|
||||||
super("dokploy:index:WebService", name, {}, opts);
|
super("dokploy:index:WebService", name, {}, opts);
|
||||||
const child = { parent: this };
|
const child = { parent: this };
|
||||||
|
|
||||||
this.application = new dokploy.Application(name, {
|
this.application = new dokploy.Application(
|
||||||
name,
|
name,
|
||||||
environmentId: args.environmentId,
|
{
|
||||||
description: args.description,
|
name,
|
||||||
env: args.env,
|
environmentId: args.environmentId,
|
||||||
replicas: args.replicas,
|
description: args.description,
|
||||||
memoryLimit: args.memoryLimit,
|
env: args.env,
|
||||||
cpuLimit: args.cpuLimit,
|
replicas: args.replicas,
|
||||||
...(args.github
|
memoryLimit: args.memoryLimit,
|
||||||
? {
|
cpuLimit: args.cpuLimit,
|
||||||
sourceType: "github",
|
...(args.github
|
||||||
buildType: "nixpacks",
|
? {
|
||||||
autoDeploy: true,
|
sourceType: "github",
|
||||||
githubId: args.github.githubId,
|
buildType: "nixpacks",
|
||||||
owner: args.github.owner,
|
autoDeploy: true,
|
||||||
repository: args.github.repository,
|
githubId: args.github.githubId,
|
||||||
branch: args.github.branch,
|
owner: args.github.owner,
|
||||||
}
|
repository: args.github.repository,
|
||||||
: {
|
branch: args.github.branch,
|
||||||
sourceType: "docker",
|
}
|
||||||
dockerImage: args.dockerImage,
|
: {
|
||||||
}),
|
sourceType: "docker",
|
||||||
}, child);
|
dockerImage: args.dockerImage,
|
||||||
|
}),
|
||||||
|
sourceType: "gitea",
|
||||||
|
buildType: "dockerfile",
|
||||||
|
autoDeploy: true,
|
||||||
|
giteaId: "test",
|
||||||
|
owner: "test",
|
||||||
|
repository: "test",
|
||||||
|
branch: "main",
|
||||||
|
},
|
||||||
|
child,
|
||||||
|
);
|
||||||
|
|
||||||
this.domains = (args.domains ?? []).map((d, i) => new dokploy.Domain(`${name}-domain-${i}`, {
|
this.domains = (args.domains ?? []).map(
|
||||||
|
(d, i) =>
|
||||||
|
new dokploy.Domain(
|
||||||
|
`${name}-domain-${i}`,
|
||||||
|
{
|
||||||
applicationId: this.application.id,
|
applicationId: this.application.id,
|
||||||
domainType: "application",
|
domainType: "application",
|
||||||
host: d.host,
|
host: d.host,
|
||||||
@@ -75,19 +93,26 @@ export class WebService extends pulumi.ComponentResource {
|
|||||||
path: d.path,
|
path: d.path,
|
||||||
https: d.https ?? true,
|
https: d.https ?? true,
|
||||||
certificateType: (d.https ?? true) ? "letsencrypt" : "none",
|
certificateType: (d.https ?? true) ? "letsencrypt" : "none",
|
||||||
}, child));
|
},
|
||||||
|
child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
if (args.basicAuth) {
|
if (args.basicAuth) {
|
||||||
new dokploy.Security(`${name}-auth`, {
|
new dokploy.Security(
|
||||||
applicationId: this.application.id,
|
`${name}-auth`,
|
||||||
username: args.basicAuth.username,
|
{
|
||||||
password: args.basicAuth.password,
|
applicationId: this.application.id,
|
||||||
}, child);
|
username: args.basicAuth.username,
|
||||||
}
|
password: args.basicAuth.password,
|
||||||
|
},
|
||||||
this.registerOutputs({
|
child,
|
||||||
application: this.application,
|
);
|
||||||
domains: this.domains,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.registerOutputs({
|
||||||
|
application: this.application,
|
||||||
|
domains: this.domains,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user