mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
1
.gitignore
vendored
1
.gitignore
vendored
@@ -35,6 +35,7 @@ yarn-error.log*
|
||||
|
||||
# Editor
|
||||
.vscode
|
||||
.idea
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
|
||||
@@ -59,7 +59,7 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
|
||||
### Premium Supporters 🥇
|
||||
|
||||
<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>
|
||||
|
||||
<!-- Elite Contributors 🥈 -->
|
||||
@@ -69,13 +69,13 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
|
||||
### Supporting Members 🥉
|
||||
|
||||
<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>
|
||||
|
||||
### Community Backers 🤝
|
||||
|
||||
<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>
|
||||
|
||||
#### Organizations:
|
||||
|
||||
@@ -32,7 +32,8 @@ The following templates are available:
|
||||
- **Metabase**: Open Source Business Intelligence
|
||||
- **Grafana**: Open Source Dashboard for your metrics
|
||||
- **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:
|
||||
|
||||
[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);
|
||||
});
|
||||
|
||||
test("Should remove web-secure if https rollback to http", () => {
|
||||
test("Should remove websecure if https rollback to http", () => {
|
||||
const originalConfig: FileConfig = loadOrCreateConfig("dokploy");
|
||||
|
||||
updateServerTraefik(
|
||||
|
||||
@@ -40,6 +40,7 @@ const baseApp: ApplicationNested = {
|
||||
placementSwarm: null,
|
||||
ports: [],
|
||||
projectId: "",
|
||||
publishDirectory: null,
|
||||
redirects: [],
|
||||
refreshToken: "",
|
||||
registry: null,
|
||||
@@ -54,6 +55,7 @@ const baseApp: ApplicationNested = {
|
||||
title: null,
|
||||
updateConfigSwarm: null,
|
||||
username: null,
|
||||
dockerContextPath: null,
|
||||
};
|
||||
|
||||
const baseDomain: Domain = {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
@@ -23,6 +24,7 @@ enum BuildType {
|
||||
heroku_buildpacks = "heroku_buildpacks",
|
||||
paketo_buildpacks = "paketo_buildpacks",
|
||||
nixpacks = "nixpacks",
|
||||
static = "static",
|
||||
}
|
||||
|
||||
const mySchema = z.discriminatedUnion("buildType", [
|
||||
@@ -34,6 +36,7 @@ const mySchema = z.discriminatedUnion("buildType", [
|
||||
invalid_type_error: "Dockerfile path is required",
|
||||
})
|
||||
.min(1, "Dockerfile required"),
|
||||
dockerContextPath: z.string().nullable().default(""),
|
||||
}),
|
||||
z.object({
|
||||
buildType: z.literal("heroku_buildpacks"),
|
||||
@@ -43,6 +46,10 @@ const mySchema = z.discriminatedUnion("buildType", [
|
||||
}),
|
||||
z.object({
|
||||
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");
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
// TODO: refactor this
|
||||
if (data.buildType === "dockerfile") {
|
||||
form.reset({
|
||||
buildType: data.buildType,
|
||||
...(data.buildType && {
|
||||
dockerfile: data.dockerfile || "",
|
||||
dockerContextPath: data.dockerContextPath || "",
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
form.reset({
|
||||
buildType: data.buildType,
|
||||
publishDirectory: data.publishDirectory || undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -93,7 +101,11 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
|
||||
await mutateAsync({
|
||||
applicationId,
|
||||
buildType: data.buildType,
|
||||
publishDirectory:
|
||||
data.buildType === "nixpacks" ? data.publishDirectory : null,
|
||||
dockerfile: data.buildType === "dockerfile" ? data.dockerfile : null,
|
||||
dockerContextPath:
|
||||
data.buildType === "dockerfile" ? data.dockerContextPath : null,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Build type saved");
|
||||
@@ -171,6 +183,12 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
|
||||
Paketo Buildpacks
|
||||
</FormLabel>
|
||||
</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>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -179,16 +197,71 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
|
||||
}}
|
||||
/>
|
||||
{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
|
||||
control={form.control}
|
||||
name="dockerfile"
|
||||
name="publishDirectory"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<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>
|
||||
<Input
|
||||
placeholder={"Path of your docker file"}
|
||||
placeholder={"Publish Directory"}
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
/>
|
||||
|
||||
@@ -114,7 +114,7 @@ export const SaveDockerProvider = ({ applicationId }: Props) => {
|
||||
<FormItem>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Password" {...field} />
|
||||
<Input placeholder="Password" {...field} type="password" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</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,
|
||||
"tag": "0026_known_dormammu",
|
||||
"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",
|
||||
"version": "v0.5.1",
|
||||
"version": "v0.6.0",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"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, {
|
||||
buildType: input.buildType,
|
||||
dockerfile: input.dockerfile,
|
||||
publishDirectory: input.publishDirectory,
|
||||
dockerContextPath: input.dockerContextPath,
|
||||
});
|
||||
|
||||
return true;
|
||||
|
||||
@@ -35,6 +35,7 @@ export const buildType = pgEnum("buildType", [
|
||||
"heroku_buildpacks",
|
||||
"paketo_buildpacks",
|
||||
"nixpacks",
|
||||
"static",
|
||||
]);
|
||||
|
||||
// TODO: refactor this types
|
||||
@@ -140,6 +141,7 @@ export const applications = pgTable("application", {
|
||||
},
|
||||
),
|
||||
dockerfile: text("dockerfile"),
|
||||
dockerContextPath: text("dockerContextPath"),
|
||||
// Drop
|
||||
dropBuildPath: text("dropBuildPath"),
|
||||
// Docker swarm json
|
||||
@@ -157,6 +159,7 @@ export const applications = pgTable("application", {
|
||||
.notNull()
|
||||
.default("idle"),
|
||||
buildType: buildType("buildType").notNull().default("nixpacks"),
|
||||
publishDirectory: text("publishDirectory"),
|
||||
createdAt: text("createdAt")
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date().toISOString()),
|
||||
@@ -315,7 +318,9 @@ const createSchema = createInsertSchema(applications, {
|
||||
"heroku_buildpacks",
|
||||
"paketo_buildpacks",
|
||||
"nixpacks",
|
||||
"static",
|
||||
]),
|
||||
publishDirectory: z.string().optional(),
|
||||
owner: z.string(),
|
||||
healthCheckSwarm: HealthCheckSwarmSchema.nullable(),
|
||||
restartPolicySwarm: RestartPolicySwarmSchema.nullable(),
|
||||
@@ -352,8 +357,10 @@ export const apiSaveBuildType = createSchema
|
||||
applicationId: true,
|
||||
buildType: true,
|
||||
dockerfile: true,
|
||||
dockerContextPath: true,
|
||||
})
|
||||
.required();
|
||||
.required()
|
||||
.merge(createSchema.pick({ publishDirectory: true }));
|
||||
|
||||
export const apiSaveGithubProvider = createSchema
|
||||
.pick({
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import type { WriteStream } from "node:fs";
|
||||
import { prepareEnvironmentVariables } from "@/server/utils/docker/utils";
|
||||
import type { ApplicationNested } from ".";
|
||||
import { getBuildAppDirectory } from "../filesystem/directory";
|
||||
import {
|
||||
getBuildAppDirectory,
|
||||
getDockerContextPath,
|
||||
} from "../filesystem/directory";
|
||||
import { spawnAsync } from "../process/spawnAsync";
|
||||
import { createEnvFile } from "./utils";
|
||||
|
||||
@@ -9,22 +12,30 @@ export const buildCustomDocker = async (
|
||||
application: ApplicationNested,
|
||||
writeStream: WriteStream,
|
||||
) => {
|
||||
const { appName, env, buildArgs } = application;
|
||||
const { appName, env, publishDirectory, buildArgs } = application;
|
||||
const dockerFilePath = getBuildAppDirectory(application);
|
||||
try {
|
||||
const image = `${appName}`;
|
||||
|
||||
const contextPath =
|
||||
const defaultContextPath =
|
||||
dockerFilePath.substring(0, dockerFilePath.lastIndexOf("/") + 1) || ".";
|
||||
const args = prepareEnvironmentVariables(buildArgs);
|
||||
|
||||
const dockerContextPath = getDockerContextPath(application);
|
||||
|
||||
const commandArgs = ["build", "-t", image, "-f", dockerFilePath, "."];
|
||||
|
||||
for (const arg of args) {
|
||||
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(
|
||||
"docker",
|
||||
commandArgs,
|
||||
@@ -34,7 +45,7 @@ export const buildCustomDocker = async (
|
||||
}
|
||||
},
|
||||
{
|
||||
cwd: contextPath,
|
||||
cwd: dockerContextPath || defaultContextPath,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import { buildCustomDocker } from "./docker-file";
|
||||
import { buildHeroku } from "./heroku";
|
||||
import { buildNixpacks } from "./nixpacks";
|
||||
import { buildPaketo } from "./paketo";
|
||||
import { buildStatic } from "./static";
|
||||
|
||||
// NIXPACKS 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);
|
||||
} else if (buildType === "dockerfile") {
|
||||
await buildCustomDocker(application, writeStream);
|
||||
} else if (buildType === "static") {
|
||||
await buildStatic(application, writeStream);
|
||||
}
|
||||
|
||||
if (application.registryId) {
|
||||
|
||||
@@ -1,18 +1,28 @@
|
||||
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 { prepareEnvironmentVariables } from "../docker/utils";
|
||||
import { getBuildAppDirectory } from "../filesystem/directory";
|
||||
import { spawnAsync } from "../process/spawnAsync";
|
||||
|
||||
// TODO: integrate in the vps sudo chown -R $(whoami) ~/.docker
|
||||
export const buildNixpacks = async (
|
||||
application: ApplicationNested,
|
||||
writeStream: WriteStream,
|
||||
) => {
|
||||
const { env, appName } = application;
|
||||
const buildAppDirectory = getBuildAppDirectory(application);
|
||||
const { env, appName, publishDirectory } = application;
|
||||
|
||||
const buildAppDirectory = getBuildAppDirectory(application);
|
||||
const buildContainerId = `${appName}-${nanoid(10)}`;
|
||||
const envVariables = prepareEnvironmentVariables(env);
|
||||
|
||||
const writeToStream = (data: string) => {
|
||||
if (writeStream.writable) {
|
||||
writeStream.write(data);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const args = ["build", buildAppDirectory, "--name", appName];
|
||||
|
||||
@@ -20,13 +30,44 @@ export const buildNixpacks = async (
|
||||
args.push("--env", env);
|
||||
}
|
||||
|
||||
await spawnAsync("nixpacks", args, (data) => {
|
||||
if (writeStream.writable) {
|
||||
writeStream.write(data);
|
||||
}
|
||||
});
|
||||
if (publishDirectory) {
|
||||
/* No need for any start command, since we'll use nginx later on */
|
||||
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;
|
||||
} catch (e) {
|
||||
await spawnAsync("docker", ["rm", buildContainerId], writeToStream);
|
||||
|
||||
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,
|
||||
}
|
||||
: {
|
||||
// if no restartPolicySwarm provided use default
|
||||
RestartPolicy: {
|
||||
Condition: "on-failure",
|
||||
},
|
||||
}),
|
||||
: {}),
|
||||
...(placementSwarm
|
||||
? {
|
||||
Placement: placementSwarm,
|
||||
|
||||
@@ -89,3 +89,12 @@ export const getBuildAppDirectory = (application: Application) => {
|
||||
|
||||
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") {
|
||||
config.http.routers[`${appName}-router-app-secure`] = {
|
||||
...currentRouterConfig,
|
||||
entryPoints: ["web-secure"],
|
||||
entryPoints: ["websecure"],
|
||||
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"],
|
||||
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