diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
new file mode 100644
index 000000000..ba25dce70
--- /dev/null
+++ b/.devcontainer/Dockerfile
@@ -0,0 +1,21 @@
+# Dockerfile for DevContainer
+FROM node:20.16.0-bullseye-slim
+
+# Install essential packages
+RUN apt-get update && apt-get install -y \
+ curl \
+ bash \
+ git \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
+
+# Set up PNPM
+ENV PNPM_HOME="/pnpm"
+ENV PATH="$PNPM_HOME:$PATH"
+RUN corepack enable && corepack prepare pnpm@9.12.0 --activate
+
+# Create workspace directory
+WORKDIR /workspaces/dokploy
+
+# Set up user permissions
+USER node
\ No newline at end of file
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 000000000..eafddd06d
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,53 @@
+{
+ "name": "Dokploy development container",
+ "build": {
+ "dockerfile": "Dockerfile",
+ "context": ".."
+ },
+ "features": {
+ "ghcr.io/devcontainers/features/docker-in-docker:2": {
+ "moby": true,
+ "version": "latest"
+ },
+ "ghcr.io/devcontainers/features/git:1": {
+ "ppa": true,
+ "version": "latest"
+ },
+ "ghcr.io/devcontainers/features/go:1": {
+ "version": "1.20"
+ }
+ },
+ "customizations": {
+ "vscode": {
+ "extensions": [
+ "ms-vscode.vscode-typescript-next",
+ "bradlc.vscode-tailwindcss",
+ "ms-vscode.vscode-json",
+ "biomejs.biome",
+ "golang.go",
+ "redhat.vscode-xml",
+ "github.vscode-github-actions",
+ "github.copilot",
+ "github.copilot-chat"
+ ]
+ }
+ },
+ "forwardPorts": [3000, 5432, 6379],
+ "portsAttributes": {
+ "3000": {
+ "label": "Dokploy App",
+ "onAutoForward": "notify"
+ },
+ "5432": {
+ "label": "PostgreSQL",
+ "onAutoForward": "silent"
+ },
+ "6379": {
+ "label": "Redis",
+ "onAutoForward": "silent"
+ }
+ },
+ "remoteUser": "node",
+ "workspaceFolder": "/workspaces/dokploy",
+ "runArgs": ["--name", "dokploy-devcontainer"]
+}
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index d45c3dac0..e210811b0 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -8,7 +8,7 @@ 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.
+- [ ] You have tested this PR in your local instance. If you have not tested it yet, please do so before submitting. This helps avoid wasting maintainers' time reviewing code that has not been verified by you.
## Issues related (if applicable)
diff --git a/.gitignore b/.gitignore
index ab2fe76c6..d531bab01 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,7 +43,4 @@ yarn-error.log*
*.pem
-.db
-
-# Development environment
-.devcontainer
\ No newline at end of file
+.db
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 4c1f832db..6ac16b14e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,7 +2,7 @@
Hey, thanks for your interest in contributing to Dokploy! We appreciate your help and taking your time to contribute.
-Before you start, please first discuss the feature/bug you want to add with the owners and comunity via github issues.
+Before you start, please first discuss the feature/bug you want to add with the owners and community via github issues.
We have a few guidelines to follow when contributing to this project:
@@ -11,6 +11,7 @@ We have a few guidelines to follow when contributing to this project:
- [Development](#development)
- [Build](#build)
- [Pull Request](#pull-request)
+- [Important Considerations](#important-considerations-for-pull-requests)
## Commit Convention
@@ -162,8 +163,9 @@ curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.39.1/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:**
+### Important Considerations for Pull Requests
+- **Testing is Mandatory:** All Pull Requests **must be tested** before submission. You must verify that your changes work as expected in a local development environment (see [Setup](#setup)). **Pull Requests that have not been tested will be closed.** This policy ensures clean contributions and reduces the time maintainers spend reviewing untested or broken code.
- **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`).
diff --git a/Dockerfile b/Dockerfile
index 5d7bb6770..262862ca6 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -65,4 +65,8 @@ RUN curl -sSL https://railpack.com/install.sh | bash
COPY --from=buildpacksio/pack:0.39.1 /usr/local/bin/pack /usr/local/bin/pack
EXPOSE 3000
-CMD [ "pnpm", "start" ]
+
+HEALTHCHECK --interval=10s --timeout=3s --retries=10 \
+ CMD curl -fs http://localhost:3000/api/trpc/settings.health || exit 1
+
+ CMD ["sh", "-c", "pnpm run wait-for-postgres && exec pnpm start"]
diff --git a/Dockerfile.schedule b/Dockerfile.schedule
index 88f38b17f..ce1f96edf 100644
--- a/Dockerfile.schedule
+++ b/Dockerfile.schedule
@@ -35,4 +35,5 @@ COPY --from=build /prod/schedules/dist ./dist
COPY --from=build /prod/schedules/package.json ./package.json
COPY --from=build /prod/schedules/node_modules ./node_modules
-CMD HOSTNAME=0.0.0.0 && pnpm start
+ENV HOSTNAME=0.0.0.0
+CMD ["pnpm", "start"]
diff --git a/Dockerfile.server b/Dockerfile.server
index 8526bc406..f5aa25c1e 100644
--- a/Dockerfile.server
+++ b/Dockerfile.server
@@ -35,4 +35,5 @@ COPY --from=build /prod/api/dist ./dist
COPY --from=build /prod/api/package.json ./package.json
COPY --from=build /prod/api/node_modules ./node_modules
-CMD HOSTNAME=0.0.0.0 && pnpm start
+ENV HOSTNAME=0.0.0.0
+CMD ["pnpm", "start"]
diff --git a/LICENSE.MD b/LICENSE.MD
index 6cbef2c6d..bcef8b36e 100644
--- a/LICENSE.MD
+++ b/LICENSE.MD
@@ -1,8 +1,13 @@
-# License
+Copyright 2026-present Dokploy Technology, Inc.
-## Core License (Apache License 2.0)
+Portions of this software are licensed as follows:
-Copyright 2025 Mauricio Siu.
+* All content that resides under a "/proprietary" directory of this repository, if that directory exists, is licensed under the license defined in "LICENSE_PROPRIETARY".
+* Content outside of the above mentioned directories or restrictions above is available under the "Apache License 2.0" license as defined below.
+
+## Apache License 2.0
+
+Copyright 2026-present Dokploy Technology, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,12 +20,4 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
-## Additional Terms for Specific Features
-The following additional terms apply to the multi-node support, Docker Compose file, Preview Deployments and Multi Server features of Dokploy. In the event of a conflict, these provisions shall take precedence over those in the Apache License:
-
-- **Self-Hosted Version Free**: All features of Dokploy, including multi-node support, Docker Compose file support, Schedules, Preview Deployments and Multi Server, will always be free to use in the self-hosted version.
-- **Restriction on Resale**: The multi-node support, Docker Compose file support, Schedules, Preview Deployments and Multi Server features cannot be sold or offered as a service by any party other than the copyright holder without prior written consent.
-- **Modification Distribution**: Any modifications to the multi-node support, Docker Compose file support, Schedules, Preview Deployments and Multi Server features must be distributed freely and cannot be sold or offered as a service.
-
-For further inquiries or permissions, please contact us directly.
diff --git a/LICENSE_PROPRIETARY.md b/LICENSE_PROPRIETARY.md
new file mode 100644
index 000000000..0f4957575
--- /dev/null
+++ b/LICENSE_PROPRIETARY.md
@@ -0,0 +1,11 @@
+The Dokploy Source Available license (DSAL) version 1.0
+
+Copyright (c) 2026-present Dokploy Technology, Inc.
+
+With regard to the Dokploy Software:This software and associated documentation files (the "Software") may only beused in production, if you (and any entity that you represent) have agreed to, and are in compliance with, a valid commercial agreement from Dokploy.Subject to the foregoing sentence, you are free to modify this Software and publish patches to the Software. You agree that Dokploy and/or its licensors (as applicable) retain all right, title and interest in and to all such modifications and/or patches, and all such modifications and/or patches may only be used, copied, modified, displayed, distributed, or otherwise exploited with a valid Dokploy Source Available License. Notwithstanding the foregoing, you may copy and modify the Software for development and testing purposes, without requiring a subscription. You agree that Dokploy and/or its licensors (as applicable) retain all right, title and interest in and to all such modifications. You are not granted any other rights beyond what is expressly stated herein. Subject to theforegoing, it is forbidden to copy, merge, publish, distribute, sublicense,and/or sell the Software.
+
+This Dokploy Source Available license applies only to the part of this Software that is in a /proprietary folder. The full text of this License shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THESOFTWARE.
+
+For all third party components incorporated into the Dokploy Software, thosecomponents are licensed under the original license provided by the owner of the applicable component.
\ No newline at end of file
diff --git a/README.md b/README.md
index e97735597..927e6ebc6 100644
--- a/README.md
+++ b/README.md
@@ -12,24 +12,8 @@
-
-
-
-
-### [Tuple, the premier screen sharing app for developers](https://tuple.app/dokploy)
-[Available for MacOS & Windows](https://tuple.app/dokploy)
| 🎖 Hero Sponsor |
-| [LX Aer](https://www.lxaer.com/?ref=dokploy) |
| 🎖 Hero Sponsor |
-| [LinkDR](https://linkdr.com/?ref=dokploy) |
| 🎖 Hero Sponsor |
-| [Awesome Tools](https://awesome.tools/) |
| 🎖 Hero Sponsor |
-| [Supafort](https://supafort.com/?ref=dokploy) |
| 🥇 Premium Supporter |
-| [Agentdock](https://agentdock.ai/?ref=dokploy) |
| 🥇 Premium Supporter |
-| [AmericanCloud](https://americancloud.com/?ref=dokploy) |
| 🥈 Elite Contributor |
-| [Tolgee](https://tolgee.io/?utm_source=github_dokploy&utm_medium=banner&utm_campaign=dokploy) |
| 🥈 Elite Contributor |
-| [Cloudblast](https://cloudblast.io/?ref=dokploy) |
| 🥉 Supporting Member |
-
-### Community Backers 🤝
-
-#### Organizations:
-
-[Sponsors on Open Collective](https://opencollective.com/dokploy)
-
-#### Individuals:
-
-[](https://opencollective.com/dokploy)
-
### Contributors 🤝
diff --git a/apps/api/package.json b/apps/api/package.json
index 0f4b1044f..71d948076 100644
--- a/apps/api/package.json
+++ b/apps/api/package.json
@@ -14,16 +14,16 @@
"@hono/node-server": "^1.14.3",
"@hono/zod-validator": "0.3.0",
"dotenv": "^16.4.5",
- "hono": "^4.7.10",
+ "hono": "^4.11.7",
"pino": "9.4.0",
"pino-pretty": "11.2.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"redis": "4.7.0",
- "zod": "^3.25.32"
+ "zod": "^3.25.76"
},
"devDependencies": {
- "@types/node": "^20.17.51",
+ "@types/node": "^20.16.0",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"tsx": "^4.16.2",
diff --git a/apps/dokploy/.env.production.example b/apps/dokploy/.env.production.example
index 41e934c3a..560faf9e6 100644
--- a/apps/dokploy/.env.production.example
+++ b/apps/dokploy/.env.production.example
@@ -1,3 +1,2 @@
-DATABASE_URL="postgres://dokploy:amukds4wi9001583845717ad2@dokploy-postgres:5432/dokploy"
PORT=3000
NODE_ENV=production
\ No newline at end of file
diff --git a/apps/dokploy/__test__/compose/domain/network-service.test.ts b/apps/dokploy/__test__/compose/domain/network-service.test.ts
index b8d03c751..83fe8a166 100644
--- a/apps/dokploy/__test__/compose/domain/network-service.test.ts
+++ b/apps/dokploy/__test__/compose/domain/network-service.test.ts
@@ -4,21 +4,30 @@ import { describe, expect, it } from "vitest";
describe("addDokployNetworkToService", () => {
it("should add network to an empty array", () => {
const result = addDokployNetworkToService([]);
- expect(result).toEqual(["dokploy-network"]);
+ expect(result).toEqual(["dokploy-network", "default"]);
});
it("should not add duplicate network to an array", () => {
const result = addDokployNetworkToService(["dokploy-network"]);
- expect(result).toEqual(["dokploy-network"]);
+ expect(result).toEqual(["dokploy-network", "default"]);
});
it("should add network to an existing array with other networks", () => {
const result = addDokployNetworkToService(["other-network"]);
- expect(result).toEqual(["other-network", "dokploy-network"]);
+ expect(result).toEqual(["other-network", "dokploy-network", "default"]);
});
it("should add network to an object if networks is an object", () => {
const result = addDokployNetworkToService({ "other-network": {} });
- expect(result).toEqual({ "other-network": {}, "dokploy-network": {} });
+ expect(result).toEqual({
+ "other-network": {},
+ "dokploy-network": {},
+ default: {},
+ });
+ });
+
+ it("should not duplicate default network when already present", () => {
+ const result = addDokployNetworkToService(["default", "dokploy-network"]);
+ expect(result).toEqual(["default", "dokploy-network"]);
});
});
diff --git a/apps/dokploy/__test__/deploy/github.test.ts b/apps/dokploy/__test__/deploy/github.test.ts
index 46be44883..d2e773dfc 100644
--- a/apps/dokploy/__test__/deploy/github.test.ts
+++ b/apps/dokploy/__test__/deploy/github.test.ts
@@ -83,6 +83,14 @@ describe("GitHub Webhook Skip CI", () => {
{ commits: [{ message: "[skip ci] test" }] },
),
).toBe("[skip ci] test");
+
+ // Soft Serve
+ expect(
+ extractCommitMessage(
+ { "x-softserve-event": "push" },
+ { commits: [{ message: "[skip ci] test" }] },
+ ),
+ ).toBe("[skip ci] test");
});
it("should handle missing commit message", () => {
@@ -99,6 +107,9 @@ describe("GitHub Webhook Skip CI", () => {
expect(extractCommitMessage({ "x-gitea-event": "push" }, {})).toBe(
"NEW COMMIT",
);
+ expect(extractCommitMessage({ "x-softserve-event": "push" }, {})).toBe(
+ "NEW COMMIT",
+ );
});
});
diff --git a/apps/dokploy/__test__/deploy/soft-serve.test.ts b/apps/dokploy/__test__/deploy/soft-serve.test.ts
new file mode 100644
index 000000000..609f15dee
--- /dev/null
+++ b/apps/dokploy/__test__/deploy/soft-serve.test.ts
@@ -0,0 +1,49 @@
+import { describe, expect, it } from "vitest";
+import {
+ extractBranchName,
+ extractCommitMessage,
+ extractHash,
+ getProviderByHeader,
+} from "@/pages/api/deploy/[refreshToken]";
+
+describe("Soft Serve Webhook", () => {
+ const mockSoftServeHeaders = {
+ "x-softserve-event": "push",
+ };
+
+ const createMockBody = (message: string, hash: string, branch: string) => ({
+ event: "push",
+ ref: `refs/heads/${branch}`,
+ after: hash,
+ commits: [{ message: message }],
+ });
+ const message: string = "feat: add new feature";
+ const hash: string = "3c91c24ef9560bddc695bce138bf8a7094ec3df5";
+ const branch: string = "feat/add-new";
+ const goodWebhook = createMockBody(message, hash, branch);
+
+ it("should properly extract the provider name", () => {
+ expect(getProviderByHeader(mockSoftServeHeaders)).toBe("soft-serve");
+ });
+
+ it("should properly extract the commit message", () => {
+ expect(extractCommitMessage(mockSoftServeHeaders, goodWebhook)).toBe(
+ message,
+ );
+ });
+
+ it("should properly extract hash", () => {
+ expect(extractHash(mockSoftServeHeaders, goodWebhook)).toBe(hash);
+ });
+
+ it("should properly extract branch name", () => {
+ expect(extractBranchName(mockSoftServeHeaders, goodWebhook)).toBe(branch);
+ });
+
+ it("should gracefully handle invalid webhook", () => {
+ expect(getProviderByHeader({})).toBeNull();
+ expect(extractCommitMessage(mockSoftServeHeaders, {})).toBe("NEW COMMIT");
+ expect(extractHash(mockSoftServeHeaders, {})).toBe("NEW COMMIT");
+ expect(extractBranchName(mockSoftServeHeaders, {})).toBeNull();
+ });
+});
diff --git a/apps/dokploy/__test__/drop/drop.test.ts b/apps/dokploy/__test__/drop/drop.test.ts
index a67d866bf..dc795fd35 100644
--- a/apps/dokploy/__test__/drop/drop.test.ts
+++ b/apps/dokploy/__test__/drop/drop.test.ts
@@ -29,6 +29,7 @@ const baseApp: ApplicationNested = {
applicationId: "",
previewLabels: [],
createEnvFile: true,
+ bitbucketRepositorySlug: "",
herokuVersion: "",
giteaBranch: "",
buildServerId: "",
@@ -146,6 +147,7 @@ const baseApp: ApplicationNested = {
dockerContextPath: null,
rollbackActive: false,
stopGracePeriodSwarm: null,
+ ulimitsSwarm: null,
};
describe("unzipDrop using real zip files", () => {
diff --git a/apps/dokploy/__test__/env/environment-access-fallback.test.ts b/apps/dokploy/__test__/env/environment-access-fallback.test.ts
new file mode 100644
index 000000000..a4b56393a
--- /dev/null
+++ b/apps/dokploy/__test__/env/environment-access-fallback.test.ts
@@ -0,0 +1,294 @@
+import { describe, expect, it } from "vitest";
+
+// Type definitions matching the project structure
+type Environment = {
+ environmentId: string;
+ name: string;
+ isDefault: boolean;
+};
+
+type Project = {
+ projectId: string;
+ name: string;
+ environments: Environment[];
+};
+
+/**
+ * Helper function that selects the appropriate environment for a user
+ * This matches the logic used in search-command.tsx and show.tsx
+ */
+function selectAccessibleEnvironment(
+ project: Project | null | undefined,
+): Environment | null {
+ if (!project || !project.environments || project.environments.length === 0) {
+ return null;
+ }
+
+ // Find default environment from accessible environments, or fall back to first accessible environment
+ const defaultEnvironment =
+ project.environments.find((environment) => environment.isDefault) ||
+ project.environments[0];
+
+ return defaultEnvironment || null;
+}
+
+describe("Environment Access Fallback", () => {
+ describe("selectAccessibleEnvironment", () => {
+ it("should return default environment when user has access to it", () => {
+ const project: Project = {
+ projectId: "proj-1",
+ name: "Test Project",
+ environments: [
+ {
+ environmentId: "env-prod",
+ name: "production",
+ isDefault: true,
+ },
+ {
+ environmentId: "env-dev",
+ name: "development",
+ isDefault: false,
+ },
+ ],
+ };
+
+ const result = selectAccessibleEnvironment(project);
+
+ expect(result).not.toBeNull();
+ expect(result?.environmentId).toBe("env-prod");
+ expect(result?.isDefault).toBe(true);
+ });
+
+ it("should return first accessible environment when user doesn't have access to default", () => {
+ // Simulating filtered environments (user only has access to development)
+ const project: Project = {
+ projectId: "proj-1",
+ name: "Test Project",
+ environments: [
+ // Note: production is not in the list because user doesn't have access
+ {
+ environmentId: "env-dev",
+ name: "development",
+ isDefault: false,
+ },
+ {
+ environmentId: "env-staging",
+ name: "staging",
+ isDefault: false,
+ },
+ ],
+ };
+
+ const result = selectAccessibleEnvironment(project);
+
+ expect(result).not.toBeNull();
+ expect(result?.environmentId).toBe("env-dev");
+ expect(result?.name).toBe("development");
+ });
+
+ it("should return first environment when no default is marked but environments exist", () => {
+ const project: Project = {
+ projectId: "proj-1",
+ name: "Test Project",
+ environments: [
+ {
+ environmentId: "env-dev",
+ name: "development",
+ isDefault: false,
+ },
+ {
+ environmentId: "env-staging",
+ name: "staging",
+ isDefault: false,
+ },
+ ],
+ };
+
+ const result = selectAccessibleEnvironment(project);
+
+ expect(result).not.toBeNull();
+ expect(result?.environmentId).toBe("env-dev");
+ });
+
+ it("should return null when project has no accessible environments", () => {
+ const project: Project = {
+ projectId: "proj-1",
+ name: "Test Project",
+ environments: [],
+ };
+
+ const result = selectAccessibleEnvironment(project);
+
+ expect(result).toBeNull();
+ });
+
+ it("should return null when project is null", () => {
+ const result = selectAccessibleEnvironment(null);
+
+ expect(result).toBeNull();
+ });
+
+ it("should return null when project is undefined", () => {
+ const result = selectAccessibleEnvironment(undefined);
+
+ expect(result).toBeNull();
+ });
+
+ it("should handle project with single accessible environment", () => {
+ const project: Project = {
+ projectId: "proj-1",
+ name: "Test Project",
+ environments: [
+ {
+ environmentId: "env-dev",
+ name: "development",
+ isDefault: false,
+ },
+ ],
+ };
+
+ const result = selectAccessibleEnvironment(project);
+
+ expect(result).not.toBeNull();
+ expect(result?.environmentId).toBe("env-dev");
+ });
+
+ it("should prioritize default environment even when it's not first in the array", () => {
+ const project: Project = {
+ projectId: "proj-1",
+ name: "Test Project",
+ environments: [
+ {
+ environmentId: "env-dev",
+ name: "development",
+ isDefault: false,
+ },
+ {
+ environmentId: "env-staging",
+ name: "staging",
+ isDefault: false,
+ },
+ {
+ environmentId: "env-prod",
+ name: "production",
+ isDefault: true,
+ },
+ ],
+ };
+
+ const result = selectAccessibleEnvironment(project);
+
+ expect(result).not.toBeNull();
+ expect(result?.environmentId).toBe("env-prod");
+ expect(result?.isDefault).toBe(true);
+ });
+
+ it("should handle multiple default environments by returning the first one found", () => {
+ // Edge case: multiple environments marked as default (shouldn't happen, but test it)
+ const project: Project = {
+ projectId: "proj-1",
+ name: "Test Project",
+ environments: [
+ {
+ environmentId: "env-prod-1",
+ name: "production-1",
+ isDefault: true,
+ },
+ {
+ environmentId: "env-prod-2",
+ name: "production-2",
+ isDefault: true,
+ },
+ ],
+ };
+
+ const result = selectAccessibleEnvironment(project);
+
+ expect(result).not.toBeNull();
+ expect(result?.isDefault).toBe(true);
+ // Should return the first default found
+ expect(result?.environmentId).toBe("env-prod-1");
+ });
+
+ it("should work correctly when user has access to multiple environments including default", () => {
+ const project: Project = {
+ projectId: "proj-1",
+ name: "Test Project",
+ environments: [
+ {
+ environmentId: "env-prod",
+ name: "production",
+ isDefault: true,
+ },
+ {
+ environmentId: "env-dev",
+ name: "development",
+ isDefault: false,
+ },
+ {
+ environmentId: "env-staging",
+ name: "staging",
+ isDefault: false,
+ },
+ ],
+ };
+
+ const result = selectAccessibleEnvironment(project);
+
+ expect(result).not.toBeNull();
+ expect(result?.environmentId).toBe("env-prod");
+ expect(result?.isDefault).toBe(true);
+ });
+
+ it("should handle real-world scenario: user with only development access", () => {
+ // This simulates the exact bug we're fixing:
+ // User has access to development but not production (default)
+ // The filtered environments array only contains development
+ const project: Project = {
+ projectId: "proj-1",
+ name: "My Project",
+ environments: [
+ // Only development is accessible (production was filtered out)
+ {
+ environmentId: "env-dev-123",
+ name: "development",
+ isDefault: false,
+ },
+ ],
+ };
+
+ const result = selectAccessibleEnvironment(project);
+
+ expect(result).not.toBeNull();
+ expect(result?.environmentId).toBe("env-dev-123");
+ expect(result?.name).toBe("development");
+ // Should not be null even though it's not the default
+ });
+ });
+
+ describe("Environment selection edge cases", () => {
+ it("should handle project with environments property as undefined", () => {
+ const project = {
+ projectId: "proj-1",
+ name: "Test Project",
+ environments: undefined,
+ } as unknown as Project;
+
+ const result = selectAccessibleEnvironment(project);
+
+ expect(result).toBeNull();
+ });
+
+ it("should handle project with null environments array", () => {
+ const project = {
+ projectId: "proj-1",
+ name: "Test Project",
+ environments: null,
+ } as unknown as Project;
+
+ const result = selectAccessibleEnvironment(project);
+
+ expect(result).toBeNull();
+ });
+ });
+});
diff --git a/apps/dokploy/__test__/server/mechanizeDockerContainer.test.ts b/apps/dokploy/__test__/server/mechanizeDockerContainer.test.ts
index c12a272bc..fb448e3af 100644
--- a/apps/dokploy/__test__/server/mechanizeDockerContainer.test.ts
+++ b/apps/dokploy/__test__/server/mechanizeDockerContainer.test.ts
@@ -6,6 +6,7 @@ type MockCreateServiceOptions = {
TaskTemplate?: {
ContainerSpec?: {
StopGracePeriod?: number;
+ Ulimits?: Array<{ Name: string; Soft: number; Hard: number }>;
};
};
[key: string]: unknown;
@@ -13,11 +14,11 @@ type MockCreateServiceOptions = {
const { inspectMock, getServiceMock, createServiceMock, getRemoteDockerMock } =
vi.hoisted(() => {
- const inspect = vi.fn<[], Promise