Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,24 @@ pnpm run dokploy:dev

Go to http://localhost:3000 to see the development server

### Teardown

When you're done, stop the Swarm services and the standalone Traefik container spun up by `dokploy:setup`:

```bash
pnpm run dokploy:teardown
```

This removes the `dokploy-postgres`, `dokploy-redis` swarm services, the `dokploy-traefik` container, and the `dokploy-network` overlay. Volumes (your local database and Redis data) are left intact — delete them manually if you want a clean slate.

### Exposing your dev server via a tunnel

If you need to expose the local dev server publicly (e.g. to test GitHub/GitLab webhooks or better-auth social callbacks), set `DEV_ALLOWED_ORIGINS` in `apps/dokploy/.env` to a comma-separated list of extra origins. Both Next's dev server and better-auth's trusted origins will pick them up:

```bash
DEV_ALLOWED_ORIGINS="https://your-tunnel.ngrok-free.app,https://your-tunnel.trycloudflare.com"
```

> [!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.

Expand Down
4 changes: 4 additions & 0 deletions apps/dokploy/.env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
DATABASE_URL="postgres://dokploy:amukds4wi9001583845717ad2@localhost:5432/dokploy"
PORT=3000
NODE_ENV=development

# Comma-separated list of extra dev origins (e.g. ngrok / trycloudflare tunnels)
# that should be trusted by better-auth and allowed by Next's dev server.
# DEV_ALLOWED_ORIGINS="https://your-tunnel.ngrok-free.app,https://your-tunnel.trycloudflare.com"
8 changes: 8 additions & 0 deletions apps/dokploy/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,16 @@
* for Docker builds.
*/

const devAllowedOrigins = (process.env.DEV_ALLOWED_ORIGINS ?? "")
.split(",")
.map((origin) => origin.trim())
.filter(Boolean);

/** @type {import("next").NextConfig} */
const nextConfig = {
...(devAllowedOrigins.length > 0 && {
allowedDevOrigins: devAllowedOrigins,
}),
reactStrictMode: true,
typescript: {
ignoreBuildErrors: true,
Expand Down
1 change: 1 addition & 0 deletions apps/dokploy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"build-server": "tsx esbuild.config.ts",
"build-next": "next build --webpack",
"setup": "tsx -r dotenv/config setup.ts && sleep 5 && pnpm run migration:run",
"teardown": "tsx -r dotenv/config teardown.ts",
"wait-for-postgres": "node -r dotenv/config dist/wait-for-postgres.mjs",
"wait-for-postgres-dev": "tsx -r dotenv/config wait-for-postgres.ts",
"reset-password": "node -r dotenv/config dist/reset-password.mjs",
Expand Down
87 changes: 87 additions & 0 deletions apps/dokploy/teardown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { exit } from "node:process";
import { docker } from "@dokploy/server/constants";

const SWARM_SERVICES = ["dokploy-postgres", "dokploy-redis"];
const STANDALONE_CONTAINERS = ["dokploy-traefik"];
const NETWORK_NAME = "dokploy-network";

const removeSwarmService = async (name: string) => {
try {
await docker.getService(name).remove();
console.log(`Removed service ${name}`);
} catch (error: any) {
if (error?.statusCode === 404) {
console.log(`Service ${name} not running`);
return;
}
console.warn(`Failed to remove service ${name}:`, error?.message ?? error);
}
};

const removeContainer = async (name: string) => {
try {
await docker.getContainer(name).remove({ force: true });
console.log(`Removed container ${name}`);
} catch (error: any) {
if (error?.statusCode === 404) {
console.log(`Container ${name} not running`);
return;
}
console.warn(
`Failed to remove container ${name}:`,
error?.message ?? error,
);
}
};

const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

const removeNetwork = async (name: string) => {
// Swarm service removal is async — tasks can still be detaching from the
// network when we get here, so retry the 403 ("in use") case briefly
// instead of forcing the user to run teardown twice.
const maxAttempts = 6;
const delayMs = 1000;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
await docker.getNetwork(name).remove();
console.log(`Removed network ${name}`);
return;
} catch (error: any) {
if (error?.statusCode === 404) {
console.log(`Network ${name} not found`);
return;
}
if (error?.statusCode === 403 && attempt < maxAttempts) {
await sleep(delayMs);
continue;
}
if (error?.statusCode === 403) {
console.log(`Network ${name} still in use, skipping`);
return;
}
console.warn(
`Failed to remove network ${name}:`,
error?.message ?? error,
);
return;
}
}
};

(async () => {
try {
for (const service of SWARM_SERVICES) {
await removeSwarmService(service);
}
for (const container of STANDALONE_CONTAINERS) {
await removeContainer(container);
}
await removeNetwork(NETWORK_NAME);
Comment thread
colocated marked this conversation as resolved.
console.log("Dokploy teardown completed");
exit(0);
} catch (error) {
console.error("Error in dokploy teardown", error);
exit(1);
}
})();
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
],
"scripts": {
"dokploy:setup": "pnpm --filter=dokploy run setup",
"dokploy:teardown": "pnpm --filter=dokploy run teardown",
"dokploy:dev": "pnpm --filter=dokploy run dev",
"dokploy:build": "pnpm --filter=dokploy run build",
"dokploy:start": "pnpm --filter=dokploy run start",
Expand Down
5 changes: 4 additions & 1 deletion packages/server/src/lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@ const { handler, api } = betterAuth({
process.env.NODE_ENV === "development"
? [
"http://localhost:3000",
"https://absolutely-handy-falcon.ngrok-free.app",
...(process.env.DEV_ALLOWED_ORIGINS ?? "")
.split(",")
.map((origin) => origin.trim())
.filter(Boolean),
]
: [];
return [
Expand Down
Loading