Last active
November 26, 2025 13:43
-
-
Save hrhv/a6be392a1d291083f12edf73c2e6da12 to your computer and use it in GitHub Desktop.
Bash script to install Dokploy on MacOS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Detect OS | |
| OS="$(uname)" | |
| # Function to detect if running in Proxmox LXC container (no-op on macOS but harmless) | |
| is_proxmox_lxc() { | |
| # Check for LXC in environment | |
| if [ -n "$container" ] && [ "$container" = "lxc" ]; then | |
| return 0 # LXC container | |
| fi | |
| # Check for LXC in /proc/1/environ (Linux only; ignore errors on macOS) | |
| if grep -q "container=lxc" /proc/1/environ 2>/dev/null; then | |
| return 0 # LXC container | |
| fi | |
| return 1 # Not LXC | |
| } | |
| install_dokploy() { | |
| if [ "$(id -u)" != "0" ]; then | |
| echo "This script must be run as root (use: sudo $0)" >&2 | |
| exit 1 | |
| fi | |
| # --- OS checks / constraints ------------------------------ | |
| # Old behavior: exit on macOS. Removed so we can run on Mac. | |
| # if [ "$(uname)" = "Darwin" ]; then | |
| # echo "This script must be run on Linux" >&2 | |
| # exit 1 | |
| # fi | |
| # Still forbid running *inside a container* | |
| if [ -f /.dockerenv ]; then | |
| echo "This script must be run on the host, not inside a container" >&2 | |
| exit 1 | |
| fi | |
| # --- Port checks (macOS-safe) ----------------------------- | |
| # macOS doesn't have `ss` by default, so use lsof/netstat instead | |
| port_in_use() { | |
| local port="$1" | |
| if command -v lsof >/dev/null 2>&1; then | |
| lsof -i :"$port" -sTCP:LISTEN >/dev/null 2>&1 | |
| else | |
| # Fallback using netstat | |
| netstat -an 2>/dev/null | grep "\.$port " | grep LISTEN >/dev/null 2>&1 | |
| fi | |
| } | |
| # check if something is running on port 80 | |
| if port_in_use 80; then | |
| echo "Error: something is already running on port 80" >&2 | |
| exit 1 | |
| fi | |
| # check if something is running on port 443 | |
| if port_in_use 443; then | |
| echo "Error: something is already running on port 443" >&2 | |
| exit 1 | |
| fi | |
| # --- Docker presence / installation ----------------------- | |
| command_exists() { | |
| command -v "$@" > /dev/null 2>&1 | |
| } | |
| if command_exists docker; then | |
| echo "Docker already installed (make sure Docker Desktop is running on macOS)." | |
| else | |
| if [ "$OS" = "Darwin" ]; then | |
| echo "Docker is not installed." | |
| echo "On macOS, please install Docker Desktop manually from:" | |
| echo " https://www.docker.com/products/docker-desktop/" | |
| exit 1 | |
| else | |
| # Linux auto-install | |
| curl -sSL https://get.docker.com | sh -s -- --version 28.5.0 | |
| fi | |
| fi | |
| # --- LXC / endpoint-mode tweak (mostly for Proxmox, no-op on Mac) --- | |
| endpoint_mode="" | |
| if is_proxmox_lxc; then | |
| echo "⚠️ WARNING: Detected Proxmox LXC container environment!" | |
| echo "Adding --endpoint-mode dnsrr to Docker service for LXC compatibility." | |
| echo "This may affect service discovery but is required for LXC containers." | |
| echo "" | |
| endpoint_mode="--endpoint-mode dnsrr" | |
| echo "Waiting for 5 seconds before continuing..." | |
| sleep 5 | |
| fi | |
| docker swarm leave --force 2>/dev/null | |
| # --- Public IP detection (for message only) --------------- | |
| get_ip() { | |
| local ip="" | |
| # Try IPv4 first | |
| ip=$(curl -4s --connect-timeout 5 https://ifconfig.io 2>/dev/null) | |
| if [ -z "$ip" ]; then | |
| ip=$(curl -4s --connect-timeout 5 https://icanhazip.com 2>/dev/null) | |
| fi | |
| if [ -z "$ip" ]; then | |
| ip=$(curl -4s --connect-timeout 5 https://ipecho.net/plain 2>/dev/null) | |
| fi | |
| # If no IPv4, try IPv6 | |
| if [ -z "$ip" ]; then | |
| ip=$(curl -6s --connect-timeout 5 https://ifconfig.io 2>/dev/null) | |
| if [ -z "$ip" ]; then | |
| ip=$(curl -6s --connect-timeout 5 https://icanhazip.com 2>/dev/null) | |
| fi | |
| if [ -z "$ip" ]; then | |
| ip=$(curl -6s --connect-timeout 5 https://ipecho.net/plain 2>/dev/null) | |
| fi | |
| fi | |
| if [ -z "$ip" ]; then | |
| echo "Error: Could not determine server IP address automatically (neither IPv4 nor IPv6)." >&2 | |
| echo "Please set the ADVERTISE_ADDR environment variable manually." >&2 | |
| echo "Example: export ADVERTISE_ADDR=<your-server-ip>" >&2 | |
| exit 1 | |
| fi | |
| echo "$ip" | |
| } | |
| # --- Private IP detection (macOS-specific handling) ------- | |
| get_private_ip() { | |
| if [ "$OS" = "Darwin" ]; then | |
| # Try common macOS interfaces | |
| for iface in en0 en1 en2; do | |
| local ip | |
| ip=$(ipconfig getifaddr "$iface" 2>/dev/null) | |
| if [ -n "$ip" ]; then | |
| echo "$ip" | |
| return 0 | |
| fi | |
| done | |
| # Fallback to loopback for Docker Desktop single-node swarm | |
| echo "127.0.0.1" | |
| else | |
| # Linux version using `ip` | |
| ip addr show | \ | |
| grep -E "inet (192\.168\.|10\.|172\.1[6-9]\.|172\.2[0-9]\.|172\.3[0-1]\.)" | \ | |
| head -n1 | awk '{print $2}' | cut -d/ -f1 | |
| fi | |
| } | |
| advertise_addr="${ADVERTISE_ADDR:-$(get_private_ip)}" | |
| if [ -z "$advertise_addr" ]; then | |
| echo "ERROR: We couldn't find a private IP address." | |
| echo "Please set the ADVERTISE_ADDR environment variable manually." | |
| echo "Example: export ADVERTISE_ADDR=192.168.1.100" | |
| exit 1 | |
| fi | |
| echo "Using advertise address: $advertise_addr" | |
| docker swarm init --advertise-addr "$advertise_addr" | |
| if [ $? -ne 0 ]; then | |
| echo "Error: Failed to initialize Docker Swarm" >&2 | |
| exit 1 | |
| fi | |
| echo "Swarm initialized" | |
| # --- Network setup ---------------------------------------- | |
| # macOS-safe: docker network rm has no -f flag; ignore errors | |
| docker network rm dokploy-network 2>/dev/null || true | |
| docker network create --driver overlay --attachable dokploy-network | |
| echo "Network created" | |
| # --- Dokploy folder --------------------------------------- | |
| mkdir -p /usr/local/dokploy | |
| chmod 777 /usr/local/dokploy | |
| # --- Services --------------------------------------------- | |
| docker service create \ | |
| --name dokploy-postgres \ | |
| --constraint 'node.role==manager' \ | |
| --network dokploy-network \ | |
| --env POSTGRES_USER=dokploy \ | |
| --env POSTGRES_DB=dokploy \ | |
| --env POSTGRES_PASSWORD=amukds4wi9001583845717ad2 \ | |
| --mount type=volume,source=dokploy-postgres-database,target=/var/lib/postgresql/data \ | |
| postgres:16 | |
| docker service create \ | |
| --name dokploy-redis \ | |
| --constraint 'node.role==manager' \ | |
| --network dokploy-network \ | |
| --mount type=volume,source=redis-data-volume,target=/data \ | |
| redis:7 | |
| # Dokploy app | |
| docker service create \ | |
| --name dokploy \ | |
| --replicas 1 \ | |
| --network dokploy-network \ | |
| --mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \ | |
| --mount type=bind,source=/usr/local/dokploy,target=/etc/dokploy \ | |
| --mount type=volume,source=dokploy-docker-config,target=/root/.docker \ | |
| --publish published=3000,target=3000,mode=host \ | |
| --update-parallelism 1 \ | |
| --update-order stop-first \ | |
| --constraint 'node.role == manager' \ | |
| $endpoint_mode \ | |
| -e ADVERTISE_ADDR="$advertise_addr" \ | |
| dokploy/dokploy:latest | |
| sleep 4 | |
| # Traefik reverse-proxy on host ports 80 & 443 | |
| docker run -d \ | |
| --name dokploy-traefik \ | |
| --restart always \ | |
| -v /etc/dokploy/traefik/traefik.yml:/etc/traefik/traefik.yml \ | |
| -v /etc/dokploy/traefik/dynamic:/etc/dokploy/traefik/dynamic \ | |
| -v /var/run/docker.sock:/var/run/docker.sock:ro \ | |
| -p 80:80/tcp \ | |
| -p 443:443/tcp \ | |
| -p 443:443/udp \ | |
| traefik:v3.6.1 | |
| docker network connect dokploy-network dokploy-traefik | |
| # --- Output ----------------------------------------------- | |
| GREEN="\033[0;32m" | |
| YELLOW="\033[1;33m" | |
| BLUE="\033[0;34m" | |
| NC="\033[0m" # No Color | |
| format_ip_for_url() { | |
| local ip="$1" | |
| if echo "$ip" | grep -q ':'; then | |
| # IPv6 | |
| echo "[${ip}]" | |
| else | |
| # IPv4 | |
| echo "${ip}" | |
| fi | |
| } | |
| public_ip="${ADVERTISE_ADDR:-$(get_ip)}" | |
| formatted_addr=$(format_ip_for_url "$public_ip") | |
| echo "" | |
| printf "${GREEN}Congratulations, Dokploy is installed!${NC}\n" | |
| printf "${BLUE}Wait 15 seconds for the server to start${NC}\n" | |
| printf "${YELLOW}Please go to http://${formatted_addr}:3000${NC}\n\n" | |
| } | |
| update_dokploy() { | |
| echo "Updating Dokploy..." | |
| # Pull the latest image | |
| docker pull dokploy/dokploy:latest | |
| # Update the service | |
| docker service update --image dokploy/dokploy:latest dokploy | |
| echo "Dokploy has been updated to the latest version." | |
| } | |
| # Main script execution | |
| if [ "$1" = "update" ]; then | |
| update_dokploy | |
| else | |
| install_dokploy | |
| fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment