mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5abcc82215 | ||
|
|
16791a9f4b | ||
|
|
54ab6e3436 | ||
|
|
f5ca72ddd7 | ||
|
|
7245e7dfd7 | ||
|
|
547d149987 | ||
|
|
3b2d29514c | ||
|
|
115abd378f | ||
|
|
9c101d78d1 | ||
|
|
610f8fa5bc | ||
|
|
e201bf12f8 | ||
|
|
bf872200a7 | ||
|
|
abc6906349 | ||
|
|
9440fd89ae | ||
|
|
bce1eb8907 | ||
|
|
4bf44b3275 | ||
|
|
06355ff089 | ||
|
|
95ecf4fe21 | ||
|
|
d50a6ce76f | ||
|
|
2d951e0b1f | ||
|
|
416de9879b | ||
|
|
082aff58a9 | ||
|
|
b74666fc2f | ||
|
|
dc626f1a94 | ||
|
|
533a5e490f | ||
|
|
cf54e4f5c2 | ||
|
|
d84c808887 | ||
|
|
89cd35adc6 | ||
|
|
6299385bb4 | ||
|
|
e6f9867500 | ||
|
|
3fdd3ddc74 | ||
|
|
6ed379243e |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -35,6 +35,7 @@ yarn-error.log*
|
|||||||
|
|
||||||
# Editor
|
# Editor
|
||||||
.vscode
|
.vscode
|
||||||
|
.idea
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
|
|||||||
### Premium Supporters 🥇
|
### Premium Supporters 🥇
|
||||||
|
|
||||||
<div style="display: flex; gap: 30px; flex-wrap: wrap;">
|
<div style="display: flex; gap: 30px; flex-wrap: wrap;">
|
||||||
<a href="https://supafort.com/" target="_blank"><img src="https://supafort.com/build/q-4Ht4rBZR.webp" alt="Supafort.com" width="190"/></a>
|
<a href="https://supafort.com/?ref=dokploy" target="_blank"><img src="https://supafort.com/build/q-4Ht4rBZR.webp" alt="Supafort.com" width="190"/></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Elite Contributors 🥈 -->
|
<!-- Elite Contributors 🥈 -->
|
||||||
@@ -69,13 +69,13 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
|
|||||||
### Supporting Members 🥉
|
### Supporting Members 🥉
|
||||||
|
|
||||||
<div style="display: flex; gap: 30px; flex-wrap: wrap;">
|
<div style="display: flex; gap: 30px; flex-wrap: wrap;">
|
||||||
<a href="https://lightspeed.run/"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a>
|
<a href="https://lightspeed.run/?ref=dokploy"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
### Community Backers 🤝
|
### Community Backers 🤝
|
||||||
|
|
||||||
<div style="display: flex; gap: 30px; flex-wrap: wrap;">
|
<div style="display: flex; gap: 30px; flex-wrap: wrap;">
|
||||||
<a href="https://steamsets.com/"><img src="https://avatars.githubusercontent.com/u/111978405?s=200&v=4" width="60px" alt="Steamsets.com"/></a>
|
<a href="https://steamsets.com/?ref=dokploy"><img src="https://avatars.githubusercontent.com/u/111978405?s=200&v=4" width="60px" alt="Steamsets.com"/></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
#### Organizations:
|
#### Organizations:
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ The following templates are available:
|
|||||||
- **Metabase**: Open Source Business Intelligence
|
- **Metabase**: Open Source Business Intelligence
|
||||||
- **Grafana**: Open Source Dashboard for your metrics
|
- **Grafana**: Open Source Dashboard for your metrics
|
||||||
- **Wordpress**: Open Source Content Management System
|
- **Wordpress**: Open Source Content Management System
|
||||||
|
- **Open WebUI**: Free and Open Source ChatGPT Alternative
|
||||||
|
- **Teable**: Open Source Airtable Alternative, Developer Friendly, No-code Database Built on Postgres
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -42,4 +43,4 @@ We accept contributions to upload new templates to the dokploy repository.
|
|||||||
|
|
||||||
Make sure to follow the guidelines for creating a template:
|
Make sure to follow the guidelines for creating a template:
|
||||||
|
|
||||||
[Steps to create your own template](https://github.com/Dokploy/dokploy/blob/canary/CONTRIBUTING.md#templates)
|
[Steps to create your own template](https://github.com/Dokploy/dokploy/blob/canary/CONTRIBUTING.md#templates)
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ test("Should not touch config without host", () => {
|
|||||||
expect(originalConfig).toEqual(config);
|
expect(originalConfig).toEqual(config);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Should remove web-secure if https rollback to http", () => {
|
test("Should remove websecure if https rollback to http", () => {
|
||||||
const originalConfig: FileConfig = loadOrCreateConfig("dokploy");
|
const originalConfig: FileConfig = loadOrCreateConfig("dokploy");
|
||||||
|
|
||||||
updateServerTraefik(
|
updateServerTraefik(
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ const baseApp: ApplicationNested = {
|
|||||||
placementSwarm: null,
|
placementSwarm: null,
|
||||||
ports: [],
|
ports: [],
|
||||||
projectId: "",
|
projectId: "",
|
||||||
|
publishDirectory: null,
|
||||||
redirects: [],
|
redirects: [],
|
||||||
refreshToken: "",
|
refreshToken: "",
|
||||||
registry: null,
|
registry: null,
|
||||||
@@ -54,6 +55,7 @@ const baseApp: ApplicationNested = {
|
|||||||
title: null,
|
title: null,
|
||||||
updateConfigSwarm: null,
|
updateConfigSwarm: null,
|
||||||
username: null,
|
username: null,
|
||||||
|
dockerContextPath: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseDomain: Domain = {
|
const baseDomain: Domain = {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
FormField,
|
FormField,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
@@ -23,6 +24,7 @@ enum BuildType {
|
|||||||
heroku_buildpacks = "heroku_buildpacks",
|
heroku_buildpacks = "heroku_buildpacks",
|
||||||
paketo_buildpacks = "paketo_buildpacks",
|
paketo_buildpacks = "paketo_buildpacks",
|
||||||
nixpacks = "nixpacks",
|
nixpacks = "nixpacks",
|
||||||
|
static = "static",
|
||||||
}
|
}
|
||||||
|
|
||||||
const mySchema = z.discriminatedUnion("buildType", [
|
const mySchema = z.discriminatedUnion("buildType", [
|
||||||
@@ -34,6 +36,7 @@ const mySchema = z.discriminatedUnion("buildType", [
|
|||||||
invalid_type_error: "Dockerfile path is required",
|
invalid_type_error: "Dockerfile path is required",
|
||||||
})
|
})
|
||||||
.min(1, "Dockerfile required"),
|
.min(1, "Dockerfile required"),
|
||||||
|
dockerContextPath: z.string().nullable().default(""),
|
||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
buildType: z.literal("heroku_buildpacks"),
|
buildType: z.literal("heroku_buildpacks"),
|
||||||
@@ -43,6 +46,10 @@ const mySchema = z.discriminatedUnion("buildType", [
|
|||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
buildType: z.literal("nixpacks"),
|
buildType: z.literal("nixpacks"),
|
||||||
|
publishDirectory: z.string().optional(),
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
buildType: z.literal("static"),
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -73,17 +80,18 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
|
|||||||
const buildType = form.watch("buildType");
|
const buildType = form.watch("buildType");
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
// TODO: refactor this
|
|
||||||
if (data.buildType === "dockerfile") {
|
if (data.buildType === "dockerfile") {
|
||||||
form.reset({
|
form.reset({
|
||||||
buildType: data.buildType,
|
buildType: data.buildType,
|
||||||
...(data.buildType && {
|
...(data.buildType && {
|
||||||
dockerfile: data.dockerfile || "",
|
dockerfile: data.dockerfile || "",
|
||||||
|
dockerContextPath: data.dockerContextPath || "",
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
form.reset({
|
form.reset({
|
||||||
buildType: data.buildType,
|
buildType: data.buildType,
|
||||||
|
publishDirectory: data.publishDirectory || undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,7 +101,11 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
|
|||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
applicationId,
|
applicationId,
|
||||||
buildType: data.buildType,
|
buildType: data.buildType,
|
||||||
|
publishDirectory:
|
||||||
|
data.buildType === "nixpacks" ? data.publishDirectory : null,
|
||||||
dockerfile: data.buildType === "dockerfile" ? data.dockerfile : null,
|
dockerfile: data.buildType === "dockerfile" ? data.dockerfile : null,
|
||||||
|
dockerContextPath:
|
||||||
|
data.buildType === "dockerfile" ? data.dockerContextPath : null,
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Build type saved");
|
toast.success("Build type saved");
|
||||||
@@ -171,6 +183,12 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
|
|||||||
Paketo Buildpacks
|
Paketo Buildpacks
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||||
|
<FormControl>
|
||||||
|
<RadioGroupItem value="static" />
|
||||||
|
</FormControl>
|
||||||
|
<FormLabel className="font-normal">Static</FormLabel>
|
||||||
|
</FormItem>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@@ -179,16 +197,71 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{buildType === "dockerfile" && (
|
{buildType === "dockerfile" && (
|
||||||
|
<>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="dockerfile"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Docker File</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder={"Path of your docker file"}
|
||||||
|
{...field}
|
||||||
|
value={field.value ?? ""}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="dockerContextPath"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Docker Context Path</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder={
|
||||||
|
"Path of your docker context default: ."
|
||||||
|
}
|
||||||
|
{...field}
|
||||||
|
value={field.value ?? ""}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{buildType === "nixpacks" && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="dockerfile"
|
name="publishDirectory"
|
||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
return (
|
return (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Docker File</FormLabel>
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>Publish Directory</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
Allows you to serve a single directory via NGINX after
|
||||||
|
the build phase. Useful if the final build assets
|
||||||
|
should be served as a static site.
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
placeholder={"Path of your docker file"}
|
placeholder={"Publish Directory"}
|
||||||
{...field}
|
{...field}
|
||||||
value={field.value ?? ""}
|
value={field.value ?? ""}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ export const SaveDockerProvider = ({ applicationId }: Props) => {
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Password</FormLabel>
|
<FormLabel>Password</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="Password" {...field} />
|
<Input placeholder="Password" {...field} type="password" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
1
apps/dokploy/drizzle/0027_red_lady_bullseye.sql
Normal file
1
apps/dokploy/drizzle/0027_red_lady_bullseye.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "application" ADD COLUMN "publishDirectory" text;
|
||||||
1
apps/dokploy/drizzle/0028_jittery_eternity.sql
Normal file
1
apps/dokploy/drizzle/0028_jittery_eternity.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TYPE "buildType" ADD VALUE 'static';
|
||||||
1
apps/dokploy/drizzle/0029_colossal_zodiak.sql
Normal file
1
apps/dokploy/drizzle/0029_colossal_zodiak.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "application" ADD COLUMN "dockerContextPath" text;
|
||||||
3016
apps/dokploy/drizzle/meta/0027_snapshot.json
Normal file
3016
apps/dokploy/drizzle/meta/0027_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
3017
apps/dokploy/drizzle/meta/0028_snapshot.json
Normal file
3017
apps/dokploy/drizzle/meta/0028_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
3023
apps/dokploy/drizzle/meta/0029_snapshot.json
Normal file
3023
apps/dokploy/drizzle/meta/0029_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -190,6 +190,27 @@
|
|||||||
"when": 1721979220929,
|
"when": 1721979220929,
|
||||||
"tag": "0026_known_dormammu",
|
"tag": "0026_known_dormammu",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 27,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1722445099203,
|
||||||
|
"tag": "0027_red_lady_bullseye",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 28,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1722503439951,
|
||||||
|
"tag": "0028_jittery_eternity",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 29,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1722578386823,
|
||||||
|
"tag": "0029_colossal_zodiak",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dokploy",
|
"name": "dokploy",
|
||||||
"version": "v0.5.1",
|
"version": "v0.6.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
BIN
apps/dokploy/public/templates/teable.png
Normal file
BIN
apps/dokploy/public/templates/teable.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 757 B |
@@ -191,6 +191,8 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
await updateApplication(input.applicationId, {
|
await updateApplication(input.applicationId, {
|
||||||
buildType: input.buildType,
|
buildType: input.buildType,
|
||||||
dockerfile: input.dockerfile,
|
dockerfile: input.dockerfile,
|
||||||
|
publishDirectory: input.publishDirectory,
|
||||||
|
dockerContextPath: input.dockerContextPath,
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export const buildType = pgEnum("buildType", [
|
|||||||
"heroku_buildpacks",
|
"heroku_buildpacks",
|
||||||
"paketo_buildpacks",
|
"paketo_buildpacks",
|
||||||
"nixpacks",
|
"nixpacks",
|
||||||
|
"static",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// TODO: refactor this types
|
// TODO: refactor this types
|
||||||
@@ -140,6 +141,7 @@ export const applications = pgTable("application", {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
dockerfile: text("dockerfile"),
|
dockerfile: text("dockerfile"),
|
||||||
|
dockerContextPath: text("dockerContextPath"),
|
||||||
// Drop
|
// Drop
|
||||||
dropBuildPath: text("dropBuildPath"),
|
dropBuildPath: text("dropBuildPath"),
|
||||||
// Docker swarm json
|
// Docker swarm json
|
||||||
@@ -157,6 +159,7 @@ export const applications = pgTable("application", {
|
|||||||
.notNull()
|
.notNull()
|
||||||
.default("idle"),
|
.default("idle"),
|
||||||
buildType: buildType("buildType").notNull().default("nixpacks"),
|
buildType: buildType("buildType").notNull().default("nixpacks"),
|
||||||
|
publishDirectory: text("publishDirectory"),
|
||||||
createdAt: text("createdAt")
|
createdAt: text("createdAt")
|
||||||
.notNull()
|
.notNull()
|
||||||
.$defaultFn(() => new Date().toISOString()),
|
.$defaultFn(() => new Date().toISOString()),
|
||||||
@@ -315,7 +318,9 @@ const createSchema = createInsertSchema(applications, {
|
|||||||
"heroku_buildpacks",
|
"heroku_buildpacks",
|
||||||
"paketo_buildpacks",
|
"paketo_buildpacks",
|
||||||
"nixpacks",
|
"nixpacks",
|
||||||
|
"static",
|
||||||
]),
|
]),
|
||||||
|
publishDirectory: z.string().optional(),
|
||||||
owner: z.string(),
|
owner: z.string(),
|
||||||
healthCheckSwarm: HealthCheckSwarmSchema.nullable(),
|
healthCheckSwarm: HealthCheckSwarmSchema.nullable(),
|
||||||
restartPolicySwarm: RestartPolicySwarmSchema.nullable(),
|
restartPolicySwarm: RestartPolicySwarmSchema.nullable(),
|
||||||
@@ -352,8 +357,10 @@ export const apiSaveBuildType = createSchema
|
|||||||
applicationId: true,
|
applicationId: true,
|
||||||
buildType: true,
|
buildType: true,
|
||||||
dockerfile: true,
|
dockerfile: true,
|
||||||
|
dockerContextPath: true,
|
||||||
})
|
})
|
||||||
.required();
|
.required()
|
||||||
|
.merge(createSchema.pick({ publishDirectory: true }));
|
||||||
|
|
||||||
export const apiSaveGithubProvider = createSchema
|
export const apiSaveGithubProvider = createSchema
|
||||||
.pick({
|
.pick({
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import type { WriteStream } from "node:fs";
|
import type { WriteStream } from "node:fs";
|
||||||
import { prepareEnvironmentVariables } from "@/server/utils/docker/utils";
|
import { prepareEnvironmentVariables } from "@/server/utils/docker/utils";
|
||||||
import type { ApplicationNested } from ".";
|
import type { ApplicationNested } from ".";
|
||||||
import { getBuildAppDirectory } from "../filesystem/directory";
|
import {
|
||||||
|
getBuildAppDirectory,
|
||||||
|
getDockerContextPath,
|
||||||
|
} from "../filesystem/directory";
|
||||||
import { spawnAsync } from "../process/spawnAsync";
|
import { spawnAsync } from "../process/spawnAsync";
|
||||||
import { createEnvFile } from "./utils";
|
import { createEnvFile } from "./utils";
|
||||||
|
|
||||||
@@ -9,22 +12,30 @@ export const buildCustomDocker = async (
|
|||||||
application: ApplicationNested,
|
application: ApplicationNested,
|
||||||
writeStream: WriteStream,
|
writeStream: WriteStream,
|
||||||
) => {
|
) => {
|
||||||
const { appName, env, buildArgs } = application;
|
const { appName, env, publishDirectory, buildArgs } = application;
|
||||||
const dockerFilePath = getBuildAppDirectory(application);
|
const dockerFilePath = getBuildAppDirectory(application);
|
||||||
try {
|
try {
|
||||||
const image = `${appName}`;
|
const image = `${appName}`;
|
||||||
|
|
||||||
const contextPath =
|
const defaultContextPath =
|
||||||
dockerFilePath.substring(0, dockerFilePath.lastIndexOf("/") + 1) || ".";
|
dockerFilePath.substring(0, dockerFilePath.lastIndexOf("/") + 1) || ".";
|
||||||
const args = prepareEnvironmentVariables(buildArgs);
|
const args = prepareEnvironmentVariables(buildArgs);
|
||||||
|
|
||||||
|
const dockerContextPath = getDockerContextPath(application);
|
||||||
|
|
||||||
const commandArgs = ["build", "-t", image, "-f", dockerFilePath, "."];
|
const commandArgs = ["build", "-t", image, "-f", dockerFilePath, "."];
|
||||||
|
|
||||||
for (const arg of args) {
|
for (const arg of args) {
|
||||||
commandArgs.push("--build-arg", arg);
|
commandArgs.push("--build-arg", arg);
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Do not generate an environment file when publishDirectory is specified,
|
||||||
|
as it could be publicly exposed.
|
||||||
|
*/
|
||||||
|
if (!publishDirectory) {
|
||||||
|
createEnvFile(dockerFilePath, env);
|
||||||
|
}
|
||||||
|
|
||||||
createEnvFile(dockerFilePath, env);
|
|
||||||
await spawnAsync(
|
await spawnAsync(
|
||||||
"docker",
|
"docker",
|
||||||
commandArgs,
|
commandArgs,
|
||||||
@@ -34,7 +45,7 @@ export const buildCustomDocker = async (
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cwd: contextPath,
|
cwd: dockerContextPath || defaultContextPath,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { buildCustomDocker } from "./docker-file";
|
|||||||
import { buildHeroku } from "./heroku";
|
import { buildHeroku } from "./heroku";
|
||||||
import { buildNixpacks } from "./nixpacks";
|
import { buildNixpacks } from "./nixpacks";
|
||||||
import { buildPaketo } from "./paketo";
|
import { buildPaketo } from "./paketo";
|
||||||
|
import { buildStatic } from "./static";
|
||||||
|
|
||||||
// NIXPACKS codeDirectory = where is the path of the code directory
|
// NIXPACKS codeDirectory = where is the path of the code directory
|
||||||
// HEROKU codeDirectory = where is the path of the code directory
|
// HEROKU codeDirectory = where is the path of the code directory
|
||||||
@@ -43,6 +44,8 @@ export const buildApplication = async (
|
|||||||
await buildPaketo(application, writeStream);
|
await buildPaketo(application, writeStream);
|
||||||
} else if (buildType === "dockerfile") {
|
} else if (buildType === "dockerfile") {
|
||||||
await buildCustomDocker(application, writeStream);
|
await buildCustomDocker(application, writeStream);
|
||||||
|
} else if (buildType === "static") {
|
||||||
|
await buildStatic(application, writeStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (application.registryId) {
|
if (application.registryId) {
|
||||||
|
|||||||
@@ -1,18 +1,28 @@
|
|||||||
import type { WriteStream } from "node:fs";
|
import type { WriteStream } from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { buildStatic } from "@/server/utils/builders/static";
|
||||||
|
import { nanoid } from "nanoid";
|
||||||
import type { ApplicationNested } from ".";
|
import type { ApplicationNested } from ".";
|
||||||
import { prepareEnvironmentVariables } from "../docker/utils";
|
import { prepareEnvironmentVariables } from "../docker/utils";
|
||||||
import { getBuildAppDirectory } from "../filesystem/directory";
|
import { getBuildAppDirectory } from "../filesystem/directory";
|
||||||
import { spawnAsync } from "../process/spawnAsync";
|
import { spawnAsync } from "../process/spawnAsync";
|
||||||
|
|
||||||
// TODO: integrate in the vps sudo chown -R $(whoami) ~/.docker
|
|
||||||
export const buildNixpacks = async (
|
export const buildNixpacks = async (
|
||||||
application: ApplicationNested,
|
application: ApplicationNested,
|
||||||
writeStream: WriteStream,
|
writeStream: WriteStream,
|
||||||
) => {
|
) => {
|
||||||
const { env, appName } = application;
|
const { env, appName, publishDirectory } = application;
|
||||||
const buildAppDirectory = getBuildAppDirectory(application);
|
|
||||||
|
|
||||||
|
const buildAppDirectory = getBuildAppDirectory(application);
|
||||||
|
const buildContainerId = `${appName}-${nanoid(10)}`;
|
||||||
const envVariables = prepareEnvironmentVariables(env);
|
const envVariables = prepareEnvironmentVariables(env);
|
||||||
|
|
||||||
|
const writeToStream = (data: string) => {
|
||||||
|
if (writeStream.writable) {
|
||||||
|
writeStream.write(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const args = ["build", buildAppDirectory, "--name", appName];
|
const args = ["build", buildAppDirectory, "--name", appName];
|
||||||
|
|
||||||
@@ -20,13 +30,44 @@ export const buildNixpacks = async (
|
|||||||
args.push("--env", env);
|
args.push("--env", env);
|
||||||
}
|
}
|
||||||
|
|
||||||
await spawnAsync("nixpacks", args, (data) => {
|
if (publishDirectory) {
|
||||||
if (writeStream.writable) {
|
/* No need for any start command, since we'll use nginx later on */
|
||||||
writeStream.write(data);
|
args.push("--no-error-without-start");
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
await spawnAsync("nixpacks", args, writeToStream);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Run the container with the image created by nixpacks,
|
||||||
|
and copy the artifacts on the host filesystem.
|
||||||
|
Then, remove the container and create a static build.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (publishDirectory) {
|
||||||
|
await spawnAsync(
|
||||||
|
"docker",
|
||||||
|
["create", "--name", buildContainerId, appName],
|
||||||
|
writeToStream,
|
||||||
|
);
|
||||||
|
|
||||||
|
await spawnAsync(
|
||||||
|
"docker",
|
||||||
|
[
|
||||||
|
"cp",
|
||||||
|
`${buildContainerId}:/app/${publishDirectory}`,
|
||||||
|
path.join(buildAppDirectory, publishDirectory),
|
||||||
|
],
|
||||||
|
writeToStream,
|
||||||
|
);
|
||||||
|
|
||||||
|
await spawnAsync("docker", ["rm", buildContainerId], writeToStream);
|
||||||
|
|
||||||
|
await buildStatic(application, writeStream);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
await spawnAsync("docker", ["rm", buildContainerId], writeToStream);
|
||||||
|
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
38
apps/dokploy/server/utils/builders/static.ts
Normal file
38
apps/dokploy/server/utils/builders/static.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import type { WriteStream } from "node:fs";
|
||||||
|
import { buildCustomDocker } from "@/server/utils/builders/docker-file";
|
||||||
|
import type { ApplicationNested } from ".";
|
||||||
|
import { createFile } from "../docker/utils";
|
||||||
|
import { getBuildAppDirectory } from "../filesystem/directory";
|
||||||
|
|
||||||
|
export const buildStatic = async (
|
||||||
|
application: ApplicationNested,
|
||||||
|
writeStream: WriteStream,
|
||||||
|
) => {
|
||||||
|
const { publishDirectory } = application;
|
||||||
|
const buildAppDirectory = getBuildAppDirectory(application);
|
||||||
|
|
||||||
|
try {
|
||||||
|
createFile(
|
||||||
|
buildAppDirectory,
|
||||||
|
"Dockerfile",
|
||||||
|
[
|
||||||
|
"FROM nginx:alpine",
|
||||||
|
"WORKDIR /usr/share/nginx/html/",
|
||||||
|
`COPY ${publishDirectory || "."} .`,
|
||||||
|
].join("\n"),
|
||||||
|
);
|
||||||
|
|
||||||
|
await buildCustomDocker(
|
||||||
|
{
|
||||||
|
...application,
|
||||||
|
buildType: "dockerfile",
|
||||||
|
dockerfile: "Dockerfile",
|
||||||
|
},
|
||||||
|
writeStream,
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -242,12 +242,7 @@ export const generateConfigContainer = (application: ApplicationNested) => {
|
|||||||
? {
|
? {
|
||||||
RestartPolicy: restartPolicySwarm,
|
RestartPolicy: restartPolicySwarm,
|
||||||
}
|
}
|
||||||
: {
|
: {}),
|
||||||
// if no restartPolicySwarm provided use default
|
|
||||||
RestartPolicy: {
|
|
||||||
Condition: "on-failure",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
...(placementSwarm
|
...(placementSwarm
|
||||||
? {
|
? {
|
||||||
Placement: placementSwarm,
|
Placement: placementSwarm,
|
||||||
|
|||||||
@@ -89,3 +89,12 @@ export const getBuildAppDirectory = (application: Application) => {
|
|||||||
|
|
||||||
return path.join(APPLICATIONS_PATH, appName, "code", buildPath ?? "");
|
return path.join(APPLICATIONS_PATH, appName, "code", buildPath ?? "");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getDockerContextPath = (application: Application) => {
|
||||||
|
const { appName, dockerContextPath } = application;
|
||||||
|
|
||||||
|
if (!dockerContextPath) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return path.join(APPLICATIONS_PATH, appName, "code", dockerContextPath);
|
||||||
|
};
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const updateServerTraefik = (
|
|||||||
if (admin?.certificateType === "letsencrypt") {
|
if (admin?.certificateType === "letsencrypt") {
|
||||||
config.http.routers[`${appName}-router-app-secure`] = {
|
config.http.routers[`${appName}-router-app-secure`] = {
|
||||||
...currentRouterConfig,
|
...currentRouterConfig,
|
||||||
entryPoints: ["web-secure"],
|
entryPoints: ["websecure"],
|
||||||
tls: { certResolver: "letsencrypt" },
|
tls: { certResolver: "letsencrypt" },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
81
apps/dokploy/templates/teable/docker-compose.yml
Normal file
81
apps/dokploy/templates/teable/docker-compose.yml
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
version: "3.9"
|
||||||
|
|
||||||
|
services:
|
||||||
|
teable:
|
||||||
|
image: ghcr.io/teableio/teable:1.3.1-alpha-build.460
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- ${TEABLE_PORT}
|
||||||
|
volumes:
|
||||||
|
- teable-data:/app/.assets
|
||||||
|
# you may use a bind-mounted host directory instead,
|
||||||
|
# so that it is harder to accidentally remove the volume and lose all your data!
|
||||||
|
# - ./docker/teable/data:/app/.assets:rw
|
||||||
|
environment:
|
||||||
|
- TZ=${TIMEZONE}
|
||||||
|
- NEXT_ENV_IMAGES_ALL_REMOTE=true
|
||||||
|
- PUBLIC_ORIGIN=${PUBLIC_ORIGIN}
|
||||||
|
- PRISMA_DATABASE_URL=${PRISMA_DATABASE_URL}
|
||||||
|
- PUBLIC_DATABASE_PROXY=${PUBLIC_DATABASE_PROXY}
|
||||||
|
- BACKEND_MAIL_HOST=${BACKEND_MAIL_HOST}
|
||||||
|
- BACKEND_MAIL_PORT=${BACKEND_MAIL_PORT}
|
||||||
|
- BACKEND_MAIL_SECURE=${BACKEND_MAIL_SECURE}
|
||||||
|
- BACKEND_MAIL_SENDER=${BACKEND_MAIL_SENDER}
|
||||||
|
- BACKEND_MAIL_SENDER_NAME=${BACKEND_MAIL_SENDER_NAME}
|
||||||
|
- BACKEND_MAIL_AUTH_USER=${BACKEND_MAIL_AUTH_USER}
|
||||||
|
- BACKEND_MAIL_AUTH_PASS=${BACKEND_MAIL_AUTH_PASS}
|
||||||
|
networks:
|
||||||
|
- dokploy-network
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.${HASH}.rule=Host(`${TEABLE_HOST}`)"
|
||||||
|
- "traefik.http.services.${HASH}.loadbalancer.server.port=${TEABLE_PORT}"
|
||||||
|
depends_on:
|
||||||
|
teable-db-migrate:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
|
||||||
|
teable-db:
|
||||||
|
image: postgres:15.4
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "${TEABLE_DB_PORT}:${POSTGRES_PORT}"
|
||||||
|
volumes:
|
||||||
|
- teable-db:/var/lib/postgresql/data
|
||||||
|
# you may use a bind-mounted host directory instead,
|
||||||
|
# so that it is harder to accidentally remove the volume and lose all your data!
|
||||||
|
# - ./docker/db/data:/var/lib/postgresql/data:rw
|
||||||
|
environment:
|
||||||
|
- TZ=${TIMEZONE}
|
||||||
|
- POSTGRES_DB=${POSTGRES_DB}
|
||||||
|
- POSTGRES_USER=${POSTGRES_USER}
|
||||||
|
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||||
|
networks:
|
||||||
|
- dokploy-network
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
[
|
||||||
|
"CMD-SHELL",
|
||||||
|
"sh -c 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}'",
|
||||||
|
]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
teable-db-migrate:
|
||||||
|
image: ghcr.io/teableio/teable-db-migrate:latest
|
||||||
|
environment:
|
||||||
|
- TZ=${TIMEZONE}
|
||||||
|
- PRISMA_DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
|
||||||
|
networks:
|
||||||
|
- dokploy-network
|
||||||
|
depends_on:
|
||||||
|
teable-db:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
networks:
|
||||||
|
dokploy-network:
|
||||||
|
external: true
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
teable-data: {}
|
||||||
|
teable-db: {}
|
||||||
48
apps/dokploy/templates/teable/index.ts
Normal file
48
apps/dokploy/templates/teable/index.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import {
|
||||||
|
type Schema,
|
||||||
|
type Template,
|
||||||
|
generateHash,
|
||||||
|
generatePassword,
|
||||||
|
generateRandomDomain,
|
||||||
|
} from "../utils";
|
||||||
|
|
||||||
|
export function generate(schema: Schema): Template {
|
||||||
|
const mainServiceHash = generateHash(schema.projectName);
|
||||||
|
const password = generatePassword();
|
||||||
|
const randomDomain = generateRandomDomain(schema);
|
||||||
|
|
||||||
|
const publicDbPort = ((min: number, max: number) => {
|
||||||
|
return Math.round(Math.random() * (max - min) + min);
|
||||||
|
})(32769, 65534);
|
||||||
|
|
||||||
|
const envs = [
|
||||||
|
`TEABLE_HOST=${randomDomain}`,
|
||||||
|
"TEABLE_PORT=3000",
|
||||||
|
`TEABLE_DB_PORT=${publicDbPort}`,
|
||||||
|
`HASH=${mainServiceHash}`,
|
||||||
|
"TIMEZONE=UTC",
|
||||||
|
"# Postgres",
|
||||||
|
"POSTGRES_HOST=teable-db",
|
||||||
|
"POSTGRES_PORT=5432",
|
||||||
|
"POSTGRES_DB=teable",
|
||||||
|
"POSTGRES_USER=teable",
|
||||||
|
`POSTGRES_PASSWORD=${password}`,
|
||||||
|
"# App",
|
||||||
|
"PUBLIC_ORIGIN=https://${TEABLE_HOST}",
|
||||||
|
"PRISMA_DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}",
|
||||||
|
"PUBLIC_DATABASE_PROXY=${TEABLE_HOST}:${TEABLE_DB_PORT}",
|
||||||
|
"# Need to support sending emails to enable the following configurations",
|
||||||
|
"# You need to modify the configuration according to the actual situation, otherwise it will not be able to send emails correctly.",
|
||||||
|
"#BACKEND_MAIL_HOST=smtp.teable.io",
|
||||||
|
"#BACKEND_MAIL_PORT=465",
|
||||||
|
"#BACKEND_MAIL_SECURE=true",
|
||||||
|
"#BACKEND_MAIL_SENDER=noreply.teable.io",
|
||||||
|
"#BACKEND_MAIL_SENDER_NAME=Teable",
|
||||||
|
"#BACKEND_MAIL_AUTH_USER=username",
|
||||||
|
"#BACKEND_MAIL_AUTH_PASS=password",
|
||||||
|
];
|
||||||
|
|
||||||
|
return {
|
||||||
|
envs,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -393,4 +393,19 @@ export const templates: TemplateData[] = [
|
|||||||
tags: ["media system"],
|
tags: ["media system"],
|
||||||
load: () => import("./jellyfin/index").then((m) => m.generate),
|
load: () => import("./jellyfin/index").then((m) => m.generate),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "teable",
|
||||||
|
name: "teable",
|
||||||
|
version: "v1.3.1-alpha-build.460",
|
||||||
|
description:
|
||||||
|
"Teable is a Super fast, Real-time, Professional, Developer friendly, No-code database built on Postgres. It uses a simple, spreadsheet-like interface to create complex enterprise-level database applications. Unlock efficient app development with no-code, free from the hurdles of data security and scalability.",
|
||||||
|
logo: "teable.png",
|
||||||
|
links: {
|
||||||
|
github: "https://github.com/teableio/teable",
|
||||||
|
website: "https://teable.io/",
|
||||||
|
docs: "https://help.teable.io/",
|
||||||
|
},
|
||||||
|
tags: ["database", "spreadsheet", "low-code", "nocode"],
|
||||||
|
load: () => import("./teable/index").then((m) => m.generate),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user