Merge pull request #4059 from Dokploy/feat/add-non-root-user

feat(servers): enhance server setup and validation for user privileges
This commit is contained in:
Mauricio Siu
2026-03-24 01:16:59 -06:00
committed by GitHub
6 changed files with 160 additions and 71 deletions

View File

@@ -20,6 +20,7 @@ import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
@@ -409,7 +410,10 @@ export const HandleServers = ({ serverId, asButton = false }: Props) => {
<FormControl>
<Input placeholder="root" {...field} />
</FormControl>
<FormDescription>
Use &quot;root&quot; or a non-root user with passwordless
sudo access.
</FormDescription>
<FormMessage />
</FormItem>
)}

View File

@@ -118,9 +118,10 @@ export const SetupServer = ({ serverId, asButton = false }: Props) => {
</div>
) : (
<div id="hook-form-add-gitlab" className="grid w-full gap-4">
<AlertBlock type="warning">
Using a root user is required to ensure everything works as
expected.
<AlertBlock type="info">
You can connect as root or as a non-root user with passwordless
sudo access. If using a non-root user, ensure passwordless sudo is
configured.
</AlertBlock>
<Tabs defaultValue="ssh-keys">

View File

@@ -163,6 +163,29 @@ export const ValidateServer = ({ serverId }: Props) => {
: "Not Created"
}
/>
<StatusRow
label="Privilege Mode"
isEnabled={
data?.privilegeMode === "root" ||
data?.privilegeMode === "sudo"
}
description={
data?.privilegeMode === "root"
? "Running as root"
: data?.privilegeMode === "sudo"
? "Running with sudo"
: "No sudo access (required for non-root)"
}
/>
<StatusRow
label="Docker Group"
isEnabled={data?.dockerGroupMember}
description={
data?.dockerGroupMember
? "User is in docker group"
: "User is not in docker group"
}
/>
</div>
</div>
</div>

View File

@@ -252,6 +252,8 @@ export const serverRouter = createTRPCRouter({
isDokployNetworkInstalled: boolean;
isSwarmInstalled: boolean;
isMainDirectoryInstalled: boolean;
privilegeMode: string;
dockerGroupMember: boolean;
};
} catch (error) {
throw new TRPCError({

View File

@@ -115,9 +115,20 @@ SYS_ARCH=$(uname -m)
CURRENT_USER=$USER
echo "Installing requirements for: OS: $OS_TYPE"
if [ $EUID != 0 ]; then
echo "Please run this script as root or with sudo ❌"
exit
# Auto-detect sudo requirement
if [ "$EUID" -eq 0 ]; then
SUDO_CMD=""
echo "Running as root"
else
if sudo -n true 2>/dev/null; then
SUDO_CMD="sudo"
echo "Running as $CURRENT_USER with sudo privileges"
else
echo "Error: Non-root user requires passwordless sudo access. ❌"
echo "Configure with: echo '$CURRENT_USER ALL=(ALL) NOPASSWD:ALL' | sudo tee /etc/sudoers.d/$CURRENT_USER"
exit 1
fi
fi
# Check if the OS is manjaro, if so, change it to arch
@@ -152,7 +163,7 @@ else
fi
if [ "$OS_TYPE" = 'amzn' ]; then
dnf install -y findutils >/dev/null
$SUDO_CMD dnf install -y findutils >/dev/null
fi
case "$OS_TYPE" in
@@ -218,6 +229,9 @@ ${installBuildpacks()}
echo -e "13. Installing Railpack"
${installRailpack()}
echo -e "14. Configuring permissions"
${setupPermissions()}
`
: `
echo -e "2. Installing Docker. "
@@ -235,6 +249,9 @@ ${installBuildpacks()}
echo -e "6. Installing Railpack"
${installRailpack()}
echo -e "7. Configuring permissions"
${setupPermissions()}
`
}
`;
@@ -352,16 +369,18 @@ const setupMainDirectory = () => `
echo "/etc/dokploy already exists ✅"
else
# Create the /etc/dokploy directory
mkdir -p /etc/dokploy
chmod 777 /etc/dokploy
$SUDO_CMD mkdir -p /etc/dokploy
echo "Directory /etc/dokploy created ✅"
fi
# Ensure the current user owns the directory
if [ -n "$SUDO_CMD" ]; then
$SUDO_CMD chown -R $CURRENT_USER:$CURRENT_USER /etc/dokploy
fi
`;
export const setupSwarm = () => `
# Check if the node is already part of a Docker Swarm
if docker info | grep -q 'Swarm: active'; then
if $SUDO_CMD docker info | grep -q 'Swarm: active'; then
echo "Already part of a Docker Swarm ✅"
else
# Get IP address
@@ -411,18 +430,18 @@ export const setupSwarm = () => `
echo "Advertise address: \$advertise_addr"
# Initialize Docker Swarm
docker swarm init --advertise-addr \$advertise_addr
$SUDO_CMD docker swarm init --advertise-addr \$advertise_addr
echo "Swarm initialized ✅"
fi
`;
const setupNetwork = () => `
# Check if the dokploy-network already exists
if docker network ls | grep -q 'dokploy-network'; then
if $SUDO_CMD docker network ls | grep -q 'dokploy-network'; then
echo "Network dokploy-network already exists ✅"
else
# Create the dokploy-network if it doesn't exist
if docker network create --driver overlay --attachable dokploy-network; then
if $SUDO_CMD docker network create --driver overlay --attachable dokploy-network; then
echo "Network created ✅"
else
echo "Failed to create dokploy-network ❌" >&2
@@ -447,33 +466,34 @@ const installUtilities = () => `
case "$OS_TYPE" in
arch)
pacman -Sy --noconfirm --needed curl wget git git-lfs jq openssl >/dev/null || true
$SUDO_CMD pacman -Sy --noconfirm --needed curl wget git git-lfs jq openssl >/dev/null || true
;;
alpine)
sed -i '/^#.*\/community/s/^#//' /etc/apk/repositories
apk update >/dev/null
apk add curl wget git git-lfs jq openssl sudo unzip tar >/dev/null
$SUDO_CMD sed -i '/^#.*\/community/s/^#//' /etc/apk/repositories
$SUDO_CMD apk update >/dev/null
$SUDO_CMD apk add curl wget git git-lfs jq openssl sudo unzip tar >/dev/null
;;
ubuntu | debian | raspbian)
DEBIAN_FRONTEND=noninteractive apt-get update -y >/dev/null
DEBIAN_FRONTEND=noninteractive apt-get install -y unzip curl wget git git-lfs jq openssl >/dev/null
export DEBIAN_FRONTEND=noninteractive
$SUDO_CMD apt-get update -y >/dev/null
$SUDO_CMD apt-get install -y unzip curl wget git git-lfs jq openssl >/dev/null
;;
centos | fedora | rhel | ol | rocky | almalinux | opencloudos | amzn)
if [ "$OS_TYPE" = "amzn" ]; then
dnf install -y wget git git-lfs jq openssl >/dev/null
$SUDO_CMD dnf install -y wget git git-lfs jq openssl >/dev/null
else
if ! command -v dnf >/dev/null; then
yum install -y dnf >/dev/null
$SUDO_CMD yum install -y dnf >/dev/null
fi
if ! command -v curl >/dev/null; then
dnf install -y curl >/dev/null
$SUDO_CMD dnf install -y curl >/dev/null
fi
dnf install -y wget git git-lfs jq openssl unzip >/dev/null
$SUDO_CMD dnf install -y wget git git-lfs jq openssl unzip >/dev/null
fi
;;
sles | opensuse-leap | opensuse-tumbleweed)
zypper refresh >/dev/null
zypper install -y curl wget git git-lfs jq openssl >/dev/null
$SUDO_CMD zypper refresh >/dev/null
$SUDO_CMD zypper install -y curl wget git git-lfs jq openssl >/dev/null
;;
*)
echo "This script only supports Debian, Redhat, Arch Linux, or SLES based operating systems for now."
@@ -500,41 +520,41 @@ if ! [ -x "$(command -v docker)" ]; then
echo " - Docker is not installed. Installing Docker. It may take a while."
case "$OS_TYPE" in
"almalinux")
dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo >/dev/null 2>&1
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin >/dev/null 2>&1
$SUDO_CMD dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo >/dev/null 2>&1
$SUDO_CMD dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin >/dev/null 2>&1
if ! [ -x "$(command -v docker)" ]; then
echo " - Docker could not be installed automatically. Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
exit 1
fi
systemctl start docker >/dev/null 2>&1
systemctl enable docker >/dev/null 2>&1
$SUDO_CMD systemctl start docker >/dev/null 2>&1
$SUDO_CMD systemctl enable docker >/dev/null 2>&1
;;
"opencloudos")
# Special handling for OpenCloud OS
echo " - Installing Docker for OpenCloud OS..."
dnf install -y docker >/dev/null 2>&1
$SUDO_CMD dnf install -y docker >/dev/null 2>&1
if ! [ -x "$(command -v docker)" ]; then
echo " - Docker could not be installed automatically. Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
exit 1
fi
# Remove --live-restore parameter from Docker configuration if it exists
if [ -f "/etc/sysconfig/docker" ]; then
echo " - Removing --live-restore parameter from Docker configuration..."
sed -i 's/--live-restore[^[:space:]]*//' /etc/sysconfig/docker >/dev/null 2>&1
sed -i 's/--live-restore//' /etc/sysconfig/docker >/dev/null 2>&1
$SUDO_CMD sed -i 's/--live-restore[^[:space:]]*//' /etc/sysconfig/docker >/dev/null 2>&1
$SUDO_CMD sed -i 's/--live-restore//' /etc/sysconfig/docker >/dev/null 2>&1
# Clean up any double spaces that might be left
sed -i 's/ */ /g' /etc/sysconfig/docker >/dev/null 2>&1
$SUDO_CMD sed -i 's/ */ /g' /etc/sysconfig/docker >/dev/null 2>&1
fi
systemctl enable docker >/dev/null 2>&1
systemctl start docker >/dev/null 2>&1
$SUDO_CMD systemctl enable docker >/dev/null 2>&1
$SUDO_CMD systemctl start docker >/dev/null 2>&1
echo " - Docker configured for OpenCloud OS"
;;
"alpine")
apk add docker docker-cli-compose >/dev/null 2>&1
rc-update add docker default >/dev/null 2>&1
service docker start >/dev/null 2>&1
$SUDO_CMD apk add docker docker-cli-compose >/dev/null 2>&1
$SUDO_CMD rc-update add docker default >/dev/null 2>&1
$SUDO_CMD service docker start >/dev/null 2>&1
if ! [ -x "$(command -v docker)" ]; then
echo " - Failed to install Docker with apk. Try to install it manually."
echo " Please visit https://wiki.alpinelinux.org/wiki/Docker for more information."
@@ -542,8 +562,8 @@ if ! [ -x "$(command -v docker)" ]; then
fi
;;
"arch")
pacman -Sy docker docker-compose --noconfirm >/dev/null 2>&1
systemctl enable docker.service >/dev/null 2>&1
$SUDO_CMD pacman -Sy docker docker-compose --noconfirm >/dev/null 2>&1
$SUDO_CMD systemctl enable docker.service >/dev/null 2>&1
if ! [ -x "$(command -v docker)" ]; then
echo " - Failed to install Docker with pacman. Try to install it manually."
echo " Please visit https://wiki.archlinux.org/title/docker for more information."
@@ -551,13 +571,13 @@ if ! [ -x "$(command -v docker)" ]; then
fi
;;
"amzn")
dnf install docker -y >/dev/null 2>&1
$SUDO_CMD dnf install docker -y >/dev/null 2>&1
DOCKER_CONFIG=/usr/local/lib/docker
mkdir -p $DOCKER_CONFIG/cli-plugins >/dev/null 2>&1
curl -sL https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o $DOCKER_CONFIG/cli-plugins/docker-compose >/dev/null 2>&1
chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose >/dev/null 2>&1
systemctl start docker >/dev/null 2>&1
systemctl enable docker >/dev/null 2>&1
$SUDO_CMD mkdir -p $DOCKER_CONFIG/cli-plugins >/dev/null 2>&1
$SUDO_CMD curl -sL https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o $DOCKER_CONFIG/cli-plugins/docker-compose >/dev/null 2>&1
$SUDO_CMD chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose >/dev/null 2>&1
$SUDO_CMD systemctl start docker >/dev/null 2>&1
$SUDO_CMD systemctl enable docker >/dev/null 2>&1
if ! [ -x "$(command -v docker)" ]; then
echo " - Failed to install Docker with dnf. Try to install it manually."
echo " Please visit https://www.cyberciti.biz/faq/how-to-install-docker-on-amazon-linux-2/ for more information."
@@ -567,18 +587,18 @@ if ! [ -x "$(command -v docker)" ]; then
"fedora")
if [ -x "$(command -v dnf5)" ]; then
# dnf5 is available
dnf config-manager addrepo --from-repofile=https://download.docker.com/linux/fedora/docker-ce.repo --overwrite >/dev/null 2>&1
$SUDO_CMD dnf config-manager addrepo --from-repofile=https://download.docker.com/linux/fedora/docker-ce.repo --overwrite >/dev/null 2>&1
else
# dnf5 is not available, use dnf
dnf config-manager --add-repo=https://download.docker.com/linux/fedora/docker-ce.repo >/dev/null 2>&1
$SUDO_CMD dnf config-manager --add-repo=https://download.docker.com/linux/fedora/docker-ce.repo >/dev/null 2>&1
fi
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin >/dev/null 2>&1
$SUDO_CMD dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin >/dev/null 2>&1
if ! [ -x "$(command -v docker)" ]; then
echo " - Docker could not be installed automatically. Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
exit 1
fi
systemctl start docker >/dev/null 2>&1
systemctl enable docker >/dev/null 2>&1
$SUDO_CMD systemctl start docker >/dev/null 2>&1
$SUDO_CMD systemctl enable docker >/dev/null 2>&1
;;
*)
if [ "$OS_TYPE" = "ubuntu" ] && [ "$OS_VERSION" = "24.10" ]; then
@@ -586,9 +606,9 @@ if ! [ -x "$(command -v docker)" ]; then
echo "Please install Docker manually."
exit 1
fi
if ! [ -x "$(command -v docker)" ]; then
curl -s https://get.docker.com | sh -s -- --version $DOCKER_VERSION 2>&1
curl -s https://get.docker.com | $SUDO_CMD sh -s -- --version $DOCKER_VERSION 2>&1
if ! [ -x "$(command -v docker)" ]; then
echo " - Docker installation failed."
echo " Maybe your OS is not supported?"
@@ -597,13 +617,13 @@ if ! [ -x "$(command -v docker)" ]; then
fi
fi
if [ "$OS_TYPE" = "rocky" ]; then
systemctl start docker >/dev/null 2>&1
systemctl enable docker >/dev/null 2>&1
$SUDO_CMD systemctl start docker >/dev/null 2>&1
$SUDO_CMD systemctl enable docker >/dev/null 2>&1
fi
if [ "$OS_TYPE" = "centos" ]; then
systemctl start docker >/dev/null 2>&1
systemctl enable docker >/dev/null 2>&1
$SUDO_CMD systemctl start docker >/dev/null 2>&1
$SUDO_CMD systemctl enable docker >/dev/null 2>&1
fi
@@ -647,7 +667,7 @@ export const installRClone = () => `
if command_exists rclone; then
echo "RClone already installed ✅"
else
curl https://rclone.org/install.sh | sudo bash
curl https://rclone.org/install.sh | $SUDO_CMD bash
RCLONE_VERSION=$(rclone --version | head -n 1 | awk '{print $2}' | sed 's/^v//')
echo "RClone version $RCLONE_VERSION installed ✅"
fi
@@ -656,19 +676,19 @@ export const installRClone = () => `
export const createTraefikInstance = () => {
const command = `
# Check if dokpyloy-traefik exists
if docker service inspect dokploy-traefik > /dev/null 2>&1; then
if $SUDO_CMD docker service inspect dokploy-traefik > /dev/null 2>&1; then
echo "Migrating Traefik to Standalone..."
docker service rm dokploy-traefik
$SUDO_CMD docker service rm dokploy-traefik
sleep 8
echo "Traefik migrated to Standalone ✅"
fi
if docker inspect dokploy-traefik > /dev/null 2>&1; then
if $SUDO_CMD docker inspect dokploy-traefik > /dev/null 2>&1; then
echo "Traefik already exists ✅"
else
# Create the dokploy-traefik container
TRAEFIK_VERSION=${TRAEFIK_VERSION}
docker run -d \
$SUDO_CMD docker run -d \
--name dokploy-traefik \
--restart always \
-v /etc/dokploy/traefik/traefik.yml:/etc/traefik/traefik.yml \
@@ -679,7 +699,7 @@ export const createTraefikInstance = () => {
-p ${TRAEFIK_HTTP3_PORT}:${TRAEFIK_HTTP3_PORT}/udp \
traefik:v$TRAEFIK_VERSION
docker network connect dokploy-network dokploy-traefik;
$SUDO_CMD docker network connect dokploy-network dokploy-traefik;
echo "Traefik version $TRAEFIK_VERSION installed ✅"
fi
`;
@@ -692,7 +712,7 @@ const installNixpacks = () => `
echo "Nixpacks already installed ✅"
else
export NIXPACKS_VERSION=1.41.0
bash -c "$(curl -fsSL https://nixpacks.com/install.sh)"
$SUDO_CMD bash -c "$(curl -fsSL https://nixpacks.com/install.sh)"
echo "Nixpacks version $NIXPACKS_VERSION installed ✅"
fi
`;
@@ -702,11 +722,28 @@ const installRailpack = () => `
echo "Railpack already installed ✅"
else
export RAILPACK_VERSION=0.15.4
bash -c "$(curl -fsSL https://railpack.com/install.sh)"
$SUDO_CMD bash -c "$(curl -fsSL https://railpack.com/install.sh)"
echo "Railpack version $RAILPACK_VERSION installed ✅"
fi
`;
const setupPermissions = () => `
# Add user to docker group if not root
if [ -n "$SUDO_CMD" ]; then
if ! groups $CURRENT_USER | grep -qw docker; then
$SUDO_CMD usermod -aG docker $CURRENT_USER
echo "User $CURRENT_USER added to docker group ✅"
else
echo "User $CURRENT_USER already in docker group ✅"
fi
# Ensure the user owns the dokploy directory
$SUDO_CMD chown -R $CURRENT_USER:$CURRENT_USER /etc/dokploy
echo "Permissions configured for $CURRENT_USER ✅"
else
echo "Running as root, no extra permissions needed ✅"
fi
`;
const installBuildpacks = () => `
SUFFIX=""
if [ "$SYS_ARCH" = "aarch64" ] || [ "$SYS_ARCH" = "arm64" ]; then
@@ -716,7 +753,7 @@ const installBuildpacks = () => `
echo "Buildpacks already installed ✅"
else
BUILDPACKS_VERSION=0.39.1
curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.39.1/pack-v$BUILDPACKS_VERSION-linux$SUFFIX.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack
curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.39.1/pack-v$BUILDPACKS_VERSION-linux$SUFFIX.tgz" | $SUDO_CMD tar -C /usr/local/bin/ --no-same-owner -xzv pack
echo "Buildpacks version $BUILDPACKS_VERSION installed ✅"
fi
`;

View File

@@ -79,6 +79,24 @@ export const validateDokployNetwork = () => `
fi
`;
export const validateSudoAccess = () => `
if [ "$(id -u)" -eq 0 ]; then
echo "root true"
elif sudo -n true 2>/dev/null; then
echo "sudo true"
else
echo "none false"
fi
`;
export const validateDockerGroup = () => `
if groups | grep -qw docker; then
echo true
else
echo false
fi
`;
export const serverValidate = async (serverId: string) => {
const client = new Client();
const server = await findServerById(serverId);
@@ -118,7 +136,11 @@ export const serverValidate = async (serverId: string) => {
isSwarmInstalled=$(${validateSwarm()})
isMainDirectoryInstalled=$(${validateMainDirectory()})
echo "{\\"docker\\": {\\"version\\": \\"$dockerVersion\\", \\"enabled\\": $dockerEnabled}, \\"rclone\\": {\\"version\\": \\"$rcloneVersion\\", \\"enabled\\": $rcloneEnabled}, \\"nixpacks\\": {\\"version\\": \\"$nixpacksVersion\\", \\"enabled\\": $nixpacksEnabled}, \\"buildpacks\\": {\\"version\\": \\"$buildpacksVersion\\", \\"enabled\\": $buildpacksEnabled}, \\"railpack\\": {\\"version\\": \\"$railpackVersion\\", \\"enabled\\": $railpackEnabled}, \\"isDokployNetworkInstalled\\": $isDokployNetworkInstalled, \\"isSwarmInstalled\\": $isSwarmInstalled, \\"isMainDirectoryInstalled\\": $isMainDirectoryInstalled}"
sudoAccessResult=$(${validateSudoAccess()})
privilegeMode=$(echo $sudoAccessResult | awk '{print $1}')
isDockerGroupMember=$(${validateDockerGroup()})
echo "{\\"docker\\": {\\"version\\": \\"$dockerVersion\\", \\"enabled\\": $dockerEnabled}, \\"rclone\\": {\\"version\\": \\"$rcloneVersion\\", \\"enabled\\": $rcloneEnabled}, \\"nixpacks\\": {\\"version\\": \\"$nixpacksVersion\\", \\"enabled\\": $nixpacksEnabled}, \\"buildpacks\\": {\\"version\\": \\"$buildpacksVersion\\", \\"enabled\\": $buildpacksEnabled}, \\"railpack\\": {\\"version\\": \\"$railpackVersion\\", \\"enabled\\": $railpackEnabled}, \\"isDokployNetworkInstalled\\": $isDokployNetworkInstalled, \\"isSwarmInstalled\\": $isSwarmInstalled, \\"isMainDirectoryInstalled\\": $isMainDirectoryInstalled, \\"privilegeMode\\": \\"$privilegeMode\\", \\"dockerGroupMember\\": $isDockerGroupMember}"
`;
client.exec(bashCommand, (err, stream) => {
if (err) {