---
title: 'Manual Installation'
description: 'Learn how to manually install Dokploy on your server.'
---

If you wish to customize the Dokploy installation on your server, you can modify several environment variables:

1. **PORT** - Ideal for avoiding conflicts with other services.
2. **TRAEFIK_SSL_PORT** - Set to another port if you want to use a different port for SSL.
3. **TRAEFIK_PORT** - Set to another port if you want to use a different port for Traefik.
4. **ADVERTISE_ADDR** - Set to another IP address if you want to use a different IP address for Swarm.
5. **RELEASE_TAG** - Set to a dokploy docker hub tag(latest, canary, feature, etc)
6. **DATABASE_URL** - Set to another database url if you want to use a different database.
7. **REDIS_HOST** - Set to another redis url if you want to use a different redis.
8. **TZ** - Set to another timezone if you want to use a different timezone.

## Installation Script

Here is a Bash script for installing Dokploy on a Linux server. Make sure you run this as root on a Linux environment that is not a container, and ensure ports 80, 443, and 3000 are free.


```bash
#!/bin/bash
install_dokploy() {
    if [ "$(id -u)" != "0" ]; then
        echo "This script must be run as root" >&2
        exit 1
    fi

    # check if is Mac OS
    if [ "$(uname)" = "Darwin" ]; then
        echo "This script must be run on Linux" >&2
        exit 1
    fi

    # check if is running inside a container
    if [ -f /.dockerenv ]; then
        echo "This script must be run on Linux" >&2
        exit 1
    fi

    # check if something is running on port 80
    if ss -tulnp | grep ':80 ' >/dev/null; then
        echo "Error: something is already running on port 80" >&2
        exit 1
    fi

    # check if something is running on port 443
    if ss -tulnp | grep ':443 ' >/dev/null; then
        echo "Error: something is already running on port 443" >&2
        exit 1
    fi

    # check if something is running on port 3000
    if ss -tulnp | grep ':3000 ' >/dev/null; then
        echo "Error: something is already running on port 3000" >&2
        echo "Dokploy requires port 3000 to be available. Please stop any service using this port." >&2
        exit 1
    fi

    command_exists() {
      command -v "$@" > /dev/null 2>&1
    }

    if command_exists docker; then
      echo "Docker already installed"
    else
      curl -sSL https://get.docker.com | sh
    fi

    docker swarm leave --force 2>/dev/null

    get_ip() {
        local ip=""
        
        # Try IPv4 first
        # First attempt: ifconfig.io
        ip=$(curl -4s --connect-timeout 5 https://ifconfig.io 2>/dev/null)
        
        # Second attempt: icanhazip.com
        if [ -z "$ip" ]; then
            ip=$(curl -4s --connect-timeout 5 https://icanhazip.com 2>/dev/null)
        fi
        
        # Third attempt: ipecho.net
        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
            # Try IPv6 with ifconfig.io
            ip=$(curl -6s --connect-timeout 5 https://ifconfig.io 2>/dev/null)
            
            # Try IPv6 with icanhazip.com
            if [ -z "$ip" ]; then
                ip=$(curl -6s --connect-timeout 5 https://icanhazip.com 2>/dev/null)
            fi
            
            # Try IPv6 with ipecho.net
            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"
    }

    advertise_addr="${ADVERTISE_ADDR:-$(get_ip)}"
    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"

    docker network rm -f dokploy-network 2>/dev/null
    docker network create --driver overlay --attachable dokploy-network

    echo "Network created"

    mkdir -p /etc/dokploy

    chmod 777 /etc/dokploy

    # Generate secure random password for Postgres
    POSTGRES_PASSWORD=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-32)
    
    # Store password as Docker Secret (encrypted and secure)
    echo "$POSTGRES_PASSWORD" | docker secret create dokploy_postgres_password - 2>/dev/null || true
    
    echo "Generated secure database credentials (stored in Docker Secrets)"

    docker service create \
    --name dokploy-postgres \
    --constraint 'node.role==manager' \
    --network dokploy-network \
    --env POSTGRES_USER=dokploy \
    --env POSTGRES_DB=dokploy \
    --secret source=dokploy_postgres_password,target=/run/secrets/postgres_password \
    --env POSTGRES_PASSWORD_FILE=/run/secrets/postgres_password \
    --mount type=volume,source=dokploy-postgres,target=/var/lib/postgresql/data \
    postgres:16

    docker service create \
    --name dokploy-redis \
    --constraint 'node.role==manager' \
    --network dokploy-network \
    --mount type=volume,source=dokploy-redis,target=/data \
    redis:7

    # Installation
    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=/etc/dokploy,target=/etc/dokploy \
      --mount type=volume,source=dokploy,target=/root/.docker \
      --secret source=dokploy_postgres_password,target=/run/secrets/postgres_password \
      --publish published=3000,target=3000,mode=host \
      --update-parallelism 1 \
      --update-order stop-first \
      --constraint 'node.role == manager' \
      -e ADVERTISE_ADDR=$advertise_addr \
      -e POSTGRES_PASSWORD_FILE=/run/secrets/postgres_password \
      dokploy/dokploy:latest


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

    docker network connect dokploy-network dokploy-traefik


    # Optional: Use docker service create instead of docker run
    #   docker service create \
    #     --name dokploy-traefik \
    #     --constraint 'node.role==manager' \
    #     --network dokploy-network \
    #     --mount type=bind,source=/etc/dokploy/traefik/traefik.yml,target=/etc/traefik/traefik.yml \
    #     --mount type=bind,source=/etc/dokploy/traefik/dynamic,target=/etc/dokploy/traefik/dynamic \
    #     --mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock,readonly \
    #     --publish mode=host,published=443,target=443 \
    #     --publish mode=host,published=80,target=80 \
    #     --publish mode=host,published=443,target=443,protocol=udp \
    #     traefik:v3.6.7

    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
    }

    formatted_addr=$(format_ip_for_url "$advertise_addr")
    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

```

This script includes checks for common pitfalls, installs Docker if it’s not already installed, initializes a Docker Swarm, creates a network, and then pulls and deploys Dokploy. After the script runs, it provides a success message and instructions for accessing Dokploy.

This structured format clearly lays out the prerequisites, steps, and post-installation information, making it user-friendly and accessible for those performing manual installations.

## Customize install 
#### Customize swarm advertise address

The --advertise-addr parameter in the docker swarm init command specifies the IP address or interface that the Docker Swarm manager node should advertise to other nodes in the Swarm. This address is used by other nodes to communicate with the manager.

By default, this script uses the external IP address of the server, obtained using the `curl -s ifconfig.me` command. However, you might need to customize this address based on your network configuration, especially if your server has multiple network interfaces or if you're setting up Swarm in a private network.

To customize the --advertise-addr parameter, replace the line: `advertise_addr=$(curl -s ifconfig.me)` with your desired IP address or interface, for example:
`advertise_addr="192.168.1.100"`

:warning: This IP address should be accessible to all nodes that will join the Swarm.


## Proxmox LXC Considerations

If you're installing Dokploy in a Proxmox LXC container, the installation script automatically detects the environment and adds `--endpoint-mode dnsrr` to Docker services for compatibility. 

For manual installations in LXC, add this flag to your service creation commands:

```bash
docker service create \
  --name dokploy-postgres \
  --endpoint-mode dnsrr \
  # ... rest of the configuration
```

<Callout type='warn'>
  **Note:** The `--endpoint-mode dnsrr` flag is required for Docker services to work properly in Proxmox LXC containers due to networking limitations.
</Callout>

## Existing Docker swarm

If you already have a Docker swarm running on your server and you want to use dokploy, you can use the following command to join it:


```bash
docker network create --driver overlay --attachable dokploy-network

mkdir -p /etc/dokploy

chmod -R 777 /etc/dokploy

docker pull dokploy/dokploy:latest

# Installation
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=/etc/dokploy,target=/etc/dokploy \
  --publish published=3000,target=3000,mode=host \
  --update-parallelism 1 \
  --update-order stop-first \
  dokploy/dokploy:latest
```

## Setup Dokploy Timezone

To setup the timezone of Dokploy, you can use the following command:

```bash
docker service update --env-add TZ=America/New_York dokploy
```



## Manual Upgrade

To upgrade Dokploy manually, you can use the following command:

```bash
curl -sSL https://dokploy.com/install.sh | sh -s update
```

### Version-Specific Installation & Updates

The installation script automatically detects the latest stable version from GitHub. You can also specify a particular version:

**Install/Update to Canary (Development):**
```bash
export DOKPLOY_VERSION=canary && curl -sSL https://dokploy.com/install.sh | sh
```

**Install/Update to Latest Stable:**
```bash
export DOKPLOY_VERSION=latest && curl -sSL https://dokploy.com/install.sh | sh
```

**Install/Update to Specific Version:**
```bash
export DOKPLOY_VERSION=v0.26.6 && curl -sSL https://dokploy.com/install.sh | sh
```

**Auto-detect Latest Stable (Default):**
```bash
curl -sSL https://dokploy.com/install.sh | sh
```

Alternatively, you can use `bash -s` for inline version specification:

```bash
DOKPLOY_VERSION=canary bash -s < <(curl -sSL https://dokploy.com/install.sh)
DOKPLOY_VERSION=v0.26.6 bash -s < <(curl -sSL https://dokploy.com/install.sh)
```

### Additional Environment Variables

**Custom Docker Swarm Network Configuration:**
```bash
export DOCKER_SWARM_INIT_ARGS="--default-addr-pool 172.20.0.0/16 --default-addr-pool-mask-length 24"
curl -sSL https://dokploy.com/install.sh | sh
```

**Manual Advertise Address:**
```bash
export ADVERTISE_ADDR=192.168.1.100
curl -sSL https://dokploy.com/install.sh | sh
```

## Updating Traefik Manually

Dokploy does not update the Traefik container automatically when you upgrade. This is intentional to avoid unexpected downtime for your services. If you need a newer Traefik version (for example, due to a breaking change or security fix), you can update it manually.

1. Remove the existing Traefik container.
2. Create a new container with the desired Traefik image version and the same configuration.
3. Connect the new container to the Dokploy network.

Example (replace `v3.6.7` with the version you want):

```bash
docker rm -f dokploy-traefik

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

docker network connect dokploy-network dokploy-traefik
```

<Callout type='warn'>
  **Breaking changes:** Some Traefik versions introduce breaking changes that may not be compatible with the configuration and structure Dokploy uses. Before upgrading, check the [Traefik release notes](https://github.com/traefik/traefik/releases) and [Dokploy releases](https://github.com/Dokploy/dokploy/releases) for any announced breaking changes. Using an incompatible version can cause routing issues (e.g. 404s for applications using domains).
</Callout>
