Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: mongo replica sets #926

Merged
Merged
Show file tree
Hide file tree
Changes from 2 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
27 changes: 27 additions & 0 deletions apps/dokploy/components/dashboard/project/add-database.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
import { slugify } from "@/lib/slug";
import { api } from "@/utils/api";
Expand Down Expand Up @@ -95,6 +96,7 @@ const mySchema = z.discriminatedUnion("type", [
.object({
type: z.literal("mongo"),
databaseUser: z.string().default("mongo"),
replicaSets: z.boolean().default(false),
})
.merge(baseDatabaseSchema),
z
Expand Down Expand Up @@ -216,6 +218,7 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {
databaseUser:
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
serverId: data.serverId,
replicaSets: data.replicaSets,
});
} else if (data.type === "redis") {
promise = redisMutation.mutateAsync({
Expand Down Expand Up @@ -540,6 +543,30 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {
);
}}
/>

{type === "mongo" && (
<FormField
control={form.control}
name="replicaSets"
render={({ field }) => {
return (
<FormItem>
<FormLabel>Use Replica Sets</FormLabel>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
disabled
Siumauricio marked this conversation as resolved.
Show resolved Hide resolved
aria-readonly
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
aria-readonly

Perhaps this should go too

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While you're at it @Siumauricio wouldn't removing this also make sense?

/>
</FormControl>

<FormMessage />
</FormItem>
);
}}
/>
)}
</div>
</div>
</form>
Expand Down
5 changes: 4 additions & 1 deletion packages/server/src/db/schema/mongo.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { relations } from "drizzle-orm";
import { integer, pgTable, text } from "drizzle-orm/pg-core";
import { boolean, integer, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
Expand Down Expand Up @@ -43,6 +43,7 @@ export const mongo = pgTable("mongo", {
serverId: text("serverId").references(() => server.serverId, {
onDelete: "cascade",
}),
replicaSets: boolean("replicaSets").default(false),
});

export const mongoRelations = relations(mongo, ({ one, many }) => ({
Expand Down Expand Up @@ -77,6 +78,7 @@ const createSchema = createInsertSchema(mongo, {
externalPort: z.number(),
description: z.string().optional(),
serverId: z.string().optional(),
replicaSets: z.boolean().default(false),
});

export const apiCreateMongo = createSchema
Expand All @@ -89,6 +91,7 @@ export const apiCreateMongo = createSchema
databaseUser: true,
databasePassword: true,
serverId: true,
replicaSets: true,
})
.required();

Expand Down
62 changes: 53 additions & 9 deletions packages/server/src/utils/databases/mongo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,64 @@ export const buildMongo = async (mongo: MongoNested) => {
databasePassword,
command,
mounts,
replicaSets,
} = mongo;

const defaultMongoEnv = `MONGO_INITDB_ROOT_USERNAME=${databaseUser}\nMONGO_INITDB_ROOT_PASSWORD=${databasePassword}${
env ? `\n${env}` : ""
}`;
const startupScript = `
#!/bin/bash
${
replicaSets
? `
mongod --port 27017 --replSet rs0 --bind_ip_all &
MONGOD_PID=$!

# Wait for MongoDB to be ready
while ! mongosh --eval "db.adminCommand('ping')" > /dev/null 2>&1; do
sleep 2
done

# Check if replica set is already initialized
REPLICA_STATUS=$(mongosh --quiet --eval "rs.status().ok || 0")

if [ "$REPLICA_STATUS" != "1" ]; then
echo "Initializing replica set..."
mongosh --eval '
rs.initiate({
_id: "rs0",
members: [{ _id: 0, host: "localhost:27017", priority: 1 }]
});

// Wait for the replica set to initialize
while (!rs.isMaster().ismaster) {
sleep(1000);
}

// Create root user after replica set is initialized and we are primary
db.getSiblingDB("admin").createUser({
user: "${databaseUser}",
pwd: "${databasePassword}",
roles: ["root"]
});
'

else
echo "Replica set already initialized."
fi
`
: "mongod --port 27017 --bind_ip_all & MONGOD_PID=$!"
}

${command ?? "wait $MONGOD_PID"}`;

const defaultMongoEnv = `MONGO_INITDB_ROOT_USERNAME=${databaseUser}\nMONGO_INITDB_ROOT_PASSWORD=${databasePassword}\nMONGO_INITDB_DATABASE=admin\n${env ? `${env}` : ""}`;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left the username and password env variables here at all times, but they will only be actually used when the non-replica set mode is used because the replica set initialization script does it otherwise.


const resources = calculateResources({
memoryLimit,
memoryReservation,
cpuLimit,
cpuReservation,
});

const envVariables = prepareEnvironmentVariables(
defaultMongoEnv,
mongo.project.env,
Expand All @@ -56,12 +103,8 @@ export const buildMongo = async (mongo: MongoNested) => {
Image: dockerImage,
Env: envVariables,
Mounts: [...volumesMount, ...bindsMount, ...filesMount],
...(command
? {
Command: ["/bin/sh"],
Args: ["-c", command],
}
: {}),
Command: ["/bin/bash"],
Args: ["-c", startupScript],
},
Networks: [{ Target: "dokploy-network" }],
Resources: {
Expand Down Expand Up @@ -90,6 +133,7 @@ export const buildMongo = async (mongo: MongoNested) => {
: [],
},
};

try {
const service = docker.getService(appName);
const inspect = await service.inspect();
Expand Down