diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000..0b849afc01 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,18 @@ +## What is this PR about? + +Please describe in a short paragraph what this PR is about. + +## Checklist + +Before submitting this PR, please make sure that: + +- [] You created a dedicated branch based on the `canary` branch. +- [] You have read the suggestions in the CONTRIBUTING.md file https://github.com/Dokploy/dokploy/blob/canary/CONTRIBUTING.md#pull-request +- [] You have tested this PR in your local instance. + +## Issues related (if applicable) + +closes #123 + +## Screenshots (if applicable) + diff --git a/.github/sponsors/tuple.png b/.github/sponsors/tuple.png new file mode 100644 index 0000000000..1d7be47ca9 Binary files /dev/null and b/.github/sponsors/tuple.png differ diff --git a/.github/workflows/create-pr.yml b/.github/workflows/create-pr.yml index e3f6aa234c..248b98d5a7 100644 --- a/.github/workflows/create-pr.yml +++ b/.github/workflows/create-pr.yml @@ -19,17 +19,14 @@ jobs: fetch-depth: 0 - name: Get version from package.json - id: package_version run: echo "VERSION=$(jq -r .version ./apps/dokploy/package.json)" >> $GITHUB_ENV - name: Get latest GitHub tag - id: latest_tag run: | LATEST_TAG=$(git ls-remote --tags origin | awk -F'/' '{print $3}' | sort -V | tail -n1) echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_ENV echo $LATEST_TAG - name: Compare versions - id: compare_versions run: | if [ "${{ env.VERSION }}" != "${{ env.LATEST_TAG }}" ]; then VERSION_CHANGED="true" @@ -42,7 +39,6 @@ jobs: echo "Latest tag: ${{ env.LATEST_TAG }}" echo "Version changed: $VERSION_CHANGED" - name: Check if a PR already exists - id: check_pr run: | PR_EXISTS=$(gh pr list --state open --base main --head canary --json number --jq '. | length') echo "PR_EXISTS=$PR_EXISTS" >> $GITHUB_ENV diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index bb7721468c..3ed957b723 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -2,7 +2,8 @@ name: Build Docker images on: push: - branches: ["canary", "main", "feat/monitoring"] + branches: [main, canary] + workflow_dispatch: jobs: build-and-push-cloud-image: diff --git a/.github/workflows/dokploy.yml b/.github/workflows/dokploy.yml index 0f65a50c99..529cd8f7fa 100644 --- a/.github/workflows/dokploy.yml +++ b/.github/workflows/dokploy.yml @@ -2,7 +2,8 @@ name: Dokploy Docker Build on: push: - branches: [main, canary, "1061-custom-docker-service-hostname"] + branches: [main, canary, "fix/re-apply-database-migration-fix"] + workflow_dispatch: env: IMAGE_NAME: dokploy/dokploy diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 827ccc709c..cfddad7b2f 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -11,12 +11,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup biomeJs uses: biomejs/setup-biome@v2 - name: Run Biome formatter - run: biome format . --write + run: biome format --write - - uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef + - uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27 # v1.3.2 diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index e9591f3cc4..6c74dbc028 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -4,36 +4,15 @@ on: pull_request: branches: [main, canary] -jobs: - lint-and-typecheck: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 - with: - node-version: 20.16.0 - cache: "pnpm" - - run: pnpm install --frozen-lockfile - - run: pnpm run server:build - - run: pnpm typecheck +permissions: + contents: read - build-and-test: - needs: lint-and-typecheck - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 - with: - node-version: 20.16.0 - cache: "pnpm" - - run: pnpm install --frozen-lockfile - - run: pnpm run server:build - - run: pnpm build - - parallel-tests: +jobs: + pr-check: runs-on: ubuntu-latest + strategy: + matrix: + job: [build, test, typecheck] steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 @@ -42,5 +21,5 @@ jobs: node-version: 20.16.0 cache: "pnpm" - run: pnpm install --frozen-lockfile - - run: pnpm run server:build - - run: pnpm test + - run: pnpm server:build + - run: pnpm ${{ matrix.job }} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000000..16e8e6664e --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["biomejs.biome"] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..99357f2369 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "editor.formatOnSave": true, + "editor.defaultFormatter": "biomejs.biome", + "editor.codeActionsOnSave": { + "source.fixAll.biome": "explicit", + "source.organizeImports.biome": "explicit" + } +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0ac5a3581b..38a36345e6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -87,7 +87,8 @@ pnpm run dokploy:dev Go to http://localhost:3000 to see the development server -Note: this project uses Biome. If your editor is configured to use another formatter such as Prettier, it's recommended to either change it to use Biome or turn it off. +> [!NOTE] +> This project uses Biome. If your editor is configured to use another formatter such as Prettier, it's recommended to either change it to use Biome or turn it off. ## Build @@ -117,10 +118,10 @@ In the case you lost your password, you can reset it using the following command pnpm run reset-password ``` -If you want to test the webhooks on development mode using localtunnel, make sure to install `localtunnel` +If you want to test the webhooks on development mode using localtunnel, make sure to install [`localtunnel`](https://localtunnel.app/) ```bash -bunx lt --port 3000 +pnpm dlx localtunnel --port 3000 ``` If you run into permission issues of docker run the following command @@ -152,7 +153,7 @@ curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.35.0/pack-v0. ## Pull Request -- The `main` branch is the source of truth and should always reflect the latest stable release. +- The `canary` branch is the source of truth and should always reflect the latest stable release. - Create a new branch for each feature or bug fix. - Make sure to add tests for your changes. - Make sure to update the documentation for any changes Go to the [docs.dokploy.com](https://docs.dokploy.com) website to see the changes. @@ -161,6 +162,12 @@ curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.35.0/pack-v0. - If your pull request fixes an open issue, please reference the issue in the pull request description. - Once your pull request is merged, you will be automatically added as a contributor to the project. +**Important Considerations for Pull Requests:** + +- **Focus and Scope:** Each Pull Request should ideally address a single, well-defined problem or introduce one new feature. This greatly facilitates review and reduces the chances of introducing unintended side effects. +- **Avoid Unfocused Changes:** Please avoid submitting Pull Requests that contain only minor changes such as whitespace adjustments, IDE-generated formatting, or removal of unused variables, unless these are part of a larger, clearly defined refactor or a dedicated "cleanup" Pull Request that addresses a specific `good first issue` or maintenance task. +- **Issue Association:** For any significant change, it's highly recommended to open an issue first to discuss the proposed solution with the community and maintainers. This ensures alignment and avoids duplicated effort. If your PR resolves an existing issue, please link it in the description (e.g., `Fixes #123`, `Closes #456`). + Thank you for your contribution! ## Templates diff --git a/Dockerfile b/Dockerfile index c41df8c730..11310b18e2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,9 @@ # syntax=docker/dockerfile:1 -FROM node:20.9-slim AS base +FROM node:20.16.0-slim AS base ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable +RUN corepack prepare pnpm@9.12.0 --activate FROM base AS build COPY . /usr/src/app @@ -57,7 +58,7 @@ RUN curl -sSL https://nixpacks.com/install.sh -o install.sh \ && pnpm install -g tsx # Install Railpack -ARG RAILPACK_VERSION=0.0.64 +ARG RAILPACK_VERSION=0.2.2 RUN curl -sSL https://railpack.com/install.sh | bash # Install buildpacks diff --git a/Dockerfile.cloud b/Dockerfile.cloud index c234259dc4..8e4bac2159 100644 --- a/Dockerfile.cloud +++ b/Dockerfile.cloud @@ -1,8 +1,9 @@ # syntax=docker/dockerfile:1 -FROM node:20.9-slim AS base +FROM node:20.16.0-slim AS base ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable +RUN corepack prepare pnpm@9.12.0 --activate FROM base AS build COPY . /usr/src/app diff --git a/Dockerfile.schedule b/Dockerfile.schedule index 70976523c5..ecb125e091 100644 --- a/Dockerfile.schedule +++ b/Dockerfile.schedule @@ -1,8 +1,9 @@ # syntax=docker/dockerfile:1 -FROM node:20.9-slim AS base +FROM node:20.16.0-slim AS base ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable +RUN corepack prepare pnpm@9.12.0 --activate FROM base AS build COPY . /usr/src/app diff --git a/Dockerfile.server b/Dockerfile.server index e911c87805..ea6b372e84 100644 --- a/Dockerfile.server +++ b/Dockerfile.server @@ -1,8 +1,9 @@ # syntax=docker/dockerfile:1 -FROM node:20.9-slim AS base +FROM node:20.16.0-slim AS base ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable +RUN corepack prepare pnpm@9.12.0 --activate FROM base AS build COPY . /usr/src/app diff --git a/GUIDES.md b/GUIDES.md index cfb7cd8128..90fba522dc 100644 --- a/GUIDES.md +++ b/GUIDES.md @@ -16,28 +16,29 @@ Here's how to install docker on different operating systems: ### Ubuntu ```bash +# Uninstall old versions +for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done + # Update package index sudo apt-get update # Install prerequisites -sudo apt-get install \ - apt-transport-https \ - ca-certificates \ - curl \ - gnupg \ - lsb-release +sudo apt-get install ca-certificates curl +sudo install -m 0755 -d /etc/apt/keyrings # Add Docker's official GPG key -curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg +sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc +sudo chmod a+r /etc/apt/keyrings/docker.asc -# Set up stable repository +# Add the repository to Apt sources echo \ - "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ - $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ + $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \ + sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # Install Docker Engine sudo apt-get update -sudo apt-get install docker-ce docker-ce-cli containerd.io +sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin ``` ## Windows diff --git a/LICENSE.MD b/LICENSE.MD index 7e49a35ba8..6cbef2c6d1 100644 --- a/LICENSE.MD +++ b/LICENSE.MD @@ -2,7 +2,7 @@ ## Core License (Apache License 2.0) -Copyright 2024 Mauricio Siu. +Copyright 2025 Mauricio Siu. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index d192d6f751..8faf22a356 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,36 @@
-
-
+
+
+ Join us on Discord for help, feedback, and discussions!
+
+
+### [Tuple, the premier screen sharing app for developers](https://tuple.app/dokploy)
+[Available for MacOS & Windows](https://tuple.app/dokploy)
-
-
-
-
-
-
-
-
-
-
-
-
+
-### Premium Supporters ๐ฅ
+
-
-
+
-
-
-
+### Premium Supporters ๐ฅ
+
-### Elite Contributors ๐ฅ
-
-
-
-
-
-
-
-
-
+### Elite Contributors ๐ฅ
+
+### Supporting Members ๐ฅ
-### Community Backers ๐ค
+
+
-## Contributing
+## ๐ค Contributing
Check out the [Contributing Guide](CONTRIBUTING.md) for more information.
diff --git a/apps/api/package.json b/apps/api/package.json
index 65f9d4ad95..dfc2a355d1 100644
--- a/apps/api/package.json
+++ b/apps/api/package.json
@@ -9,25 +9,30 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
+ "inngest": "3.40.1",
"@dokploy/server": "workspace:*",
- "@hono/node-server": "^1.12.1",
+ "@hono/node-server": "^1.14.3",
"@hono/zod-validator": "0.3.0",
"@nerimity/mimiqueue": "1.2.3",
- "dotenv": "^16.3.1",
- "hono": "^4.5.8",
+ "dotenv": "^16.4.5",
+ "hono": "^4.7.10",
"pino": "9.4.0",
"pino-pretty": "11.2.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"redis": "4.7.0",
- "zod": "^3.23.4"
+ "zod": "^3.25.32"
},
"devDependencies": {
- "@types/node": "^20.11.17",
+ "@types/node": "^20.17.51",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
- "tsx": "^4.7.1",
- "typescript": "^5.4.2"
+ "tsx": "^4.16.2",
+ "typescript": "^5.8.3"
},
- "packageManager": "pnpm@9.5.0"
+ "packageManager": "pnpm@9.12.0",
+ "engines": {
+ "node": "^20.16.0",
+ "pnpm": ">=9.12.0"
+ }
}
diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts
index 0db5659950..8ddb56dec0 100644
--- a/apps/api/src/index.ts
+++ b/apps/api/src/index.ts
@@ -2,21 +2,90 @@ import { serve } from "@hono/node-server";
import { Hono } from "hono";
import "dotenv/config";
import { zValidator } from "@hono/zod-validator";
-import { Queue } from "@nerimity/mimiqueue";
-import { createClient } from "redis";
+import { Inngest } from "inngest";
+import { serve as serveInngest } from "inngest/hono";
import { logger } from "./logger.js";
-import { type DeployJob, deployJobSchema } from "./schema.js";
+import {
+ cancelDeploymentSchema,
+ type DeployJob,
+ deployJobSchema,
+} from "./schema.js";
import { deploy } from "./utils.js";
const app = new Hono();
-const redisClient = createClient({
- url: process.env.REDIS_URL,
+
+// Initialize Inngest client
+export const inngest = new Inngest({
+ id: "dokploy-deployments",
+ name: "Dokploy Deployment Service",
});
+export const deploymentFunction = inngest.createFunction(
+ {
+ id: "deploy-application",
+ name: "Deploy Application",
+ concurrency: [
+ {
+ key: "event.data.serverId",
+ limit: 1,
+ },
+ ],
+ retries: 0,
+ cancelOn: [
+ {
+ event: "deployment/cancelled",
+ if: "async.data.applicationId == event.data.applicationId || async.data.composeId == event.data.composeId",
+ timeout: "1h", // Allow cancellation for up to 1 hour
+ },
+ ],
+ },
+ { event: "deployment/requested" },
+
+ async ({ event, step }) => {
+ const jobData = event.data as DeployJob;
+
+ return await step.run("execute-deployment", async () => {
+ logger.info("Deploying started");
+
+ try {
+ const result = await deploy(jobData);
+ logger.info("Deployment finished", result);
+
+ // Send success event
+ await inngest.send({
+ name: "deployment/completed",
+ data: {
+ ...jobData,
+ result,
+ status: "success",
+ },
+ });
+
+ return result;
+ } catch (error) {
+ logger.error("Deployment failed", { jobData, error });
+
+ // Send failure event
+ await inngest.send({
+ name: "deployment/failed",
+ data: {
+ ...jobData,
+ error: error instanceof Error ? error.message : String(error),
+ status: "failed",
+ },
+ });
+
+ throw error;
+ }
+ });
+ },
+);
+
app.use(async (c, next) => {
- if (c.req.path === "/health") {
+ if (c.req.path === "/health" || c.req.path === "/api/inngest") {
return next();
}
+
const authHeader = c.req.header("X-API-Key");
if (process.env.API_KEY !== authHeader) {
@@ -26,36 +95,97 @@ app.use(async (c, next) => {
return next();
});
-app.post("/deploy", zValidator("json", deployJobSchema), (c) => {
+app.post("/deploy", zValidator("json", deployJobSchema), async (c) => {
const data = c.req.valid("json");
- queue.add(data, { groupName: data.serverId });
- return c.json(
- {
- message: "Deployment Added",
- },
- 200,
- );
-});
+ logger.info("Received deployment request", data);
-app.get("/health", async (c) => {
- return c.json({ status: "ok" });
+ try {
+ // Send event to Inngest instead of adding to Redis queue
+ await inngest.send({
+ name: "deployment/requested",
+ data,
+ });
+
+ logger.info("Deployment event sent to Inngest", {
+ serverId: data.serverId,
+ });
+
+ return c.json(
+ {
+ message: "Deployment Added to Inngest Queue",
+ serverId: data.serverId,
+ },
+ 200,
+ );
+ } catch (error) {
+ console.log("error", error);
+ logger.error("Failed to send deployment event", error);
+ return c.json(
+ {
+ message: "Failed to queue deployment",
+ error: error instanceof Error ? error.message : String(error),
+ },
+ 500,
+ );
+ }
});
-const queue = new Queue({
- name: "deployments",
- process: async (job: DeployJob) => {
- logger.info("Deploying job", job);
- return await deploy(job);
+app.post(
+ "/cancel-deployment",
+ zValidator("json", cancelDeploymentSchema),
+ async (c) => {
+ const data = c.req.valid("json");
+ logger.info("Received cancel deployment request", data);
+
+ try {
+ // Send cancellation event to Inngest
+
+ await inngest.send({
+ name: "deployment/cancelled",
+ data,
+ });
+
+ const identifier =
+ data.applicationType === "application"
+ ? `applicationId: ${data.applicationId}`
+ : `composeId: ${data.composeId}`;
+
+ logger.info("Deployment cancellation event sent", {
+ ...data,
+ identifier,
+ });
+
+ return c.json({
+ message: "Deployment cancellation requested",
+ applicationType: data.applicationType,
+ });
+ } catch (error) {
+ logger.error("Failed to send deployment cancellation event", error);
+ return c.json(
+ {
+ message: "Failed to cancel deployment",
+ error: error instanceof Error ? error.message : String(error),
+ },
+ 500,
+ );
+ }
},
- redisClient,
+);
+
+app.get("/health", async (c) => {
+ return c.json({ status: "ok" });
});
-(async () => {
- await redisClient.connect();
- await redisClient.flushAll();
- logger.info("Redis Cleaned");
-})();
+// Serve Inngest functions endpoint
+app.on(
+ ["GET", "POST", "PUT"],
+ "/api/inngest",
+ serveInngest({
+ client: inngest,
+ functions: [deploymentFunction],
+ }),
+);
const port = Number.parseInt(process.env.PORT || "3000");
-logger.info("Starting Deployments Server โ
", port);
+logger.info("Starting Deployments Server with Inngest โ
", port);
serve({ fetch: app.fetch, port });
diff --git a/apps/api/src/schema.ts b/apps/api/src/schema.ts
index 609289bf70..5a4355956f 100644
--- a/apps/api/src/schema.ts
+++ b/apps/api/src/schema.ts
@@ -3,8 +3,8 @@ import { z } from "zod";
export const deployJobSchema = z.discriminatedUnion("applicationType", [
z.object({
applicationId: z.string(),
- titleLog: z.string(),
- descriptionLog: z.string(),
+ titleLog: z.string().optional(),
+ descriptionLog: z.string().optional(),
server: z.boolean().optional(),
type: z.enum(["deploy", "redeploy"]),
applicationType: z.literal("application"),
@@ -12,8 +12,8 @@ export const deployJobSchema = z.discriminatedUnion("applicationType", [
}),
z.object({
composeId: z.string(),
- titleLog: z.string(),
- descriptionLog: z.string(),
+ titleLog: z.string().optional(),
+ descriptionLog: z.string().optional(),
server: z.boolean().optional(),
type: z.enum(["deploy", "redeploy"]),
applicationType: z.literal("compose"),
@@ -22,8 +22,8 @@ export const deployJobSchema = z.discriminatedUnion("applicationType", [
z.object({
applicationId: z.string(),
previewDeploymentId: z.string(),
- titleLog: z.string(),
- descriptionLog: z.string(),
+ titleLog: z.string().optional(),
+ descriptionLog: z.string().optional(),
server: z.boolean().optional(),
type: z.enum(["deploy"]),
applicationType: z.literal("application-preview"),
@@ -32,3 +32,16 @@ export const deployJobSchema = z.discriminatedUnion("applicationType", [
]);
export type DeployJob = z.infer