Pterodactyl Docker Compose Installation

A few simple Docker Compose files to easily setup Pterodactyl

December 25, 2023


Pterodactyl’s great! Unfortunately the documentation on how to install Pterodactyl is still lacking.

When I was in middle school (in the United States), I tried to install Pterodactyl and ended up having to restart at least 10 (!) times because of minor mistakes. The only installations that ended up working first try were the installer scripts which often harbored their own issues.

We now live in the age where you can just use Docker and Docker Compose to deploy everything easily. It’s a shame though that the official documentation has no Docker Compose instructions to show for it.

Enough waffling. Here’s how to easily configure it.


Basic assumptions (if you know what you are doing, this guide is still fine):

  • You have a domain and it is correctly set up to point panel.domain.com and node.domain.com to your server
  • Docker is running on root (unfortunately rootless cgroupsv2 is broken with non Systemd systems)
  • Docker is running in an environment that supports cgroupsv2 to limit container resource usage
  • Firewall allows: 80/tcp, 443/tcp, 443/udp, 8080/tcp, 8080/udp, and 2022/tcp

Why open 8080/tcp and 8080/udp when we could just use 443? Pterodactyl is rather picky and using 443 exclusively does not play well with Caddy. It is doable though and I encourage you to give it a try if you think you’ve got the chops.


NOTE: If you’ve installed Docker Compose and the docker-compose command doesn’t work for you, instead use the docker compose command in place of all of the uses of docker-compose in this guide.


In the course of the guide, you should place all the individual components in separate folders. A file structre like the following should suffice:

File Structure
.
├─ 📁 panel/
│  └ 📄 docker-compose.yml
├─ 📁 wings/
│  └ 📄 docker-compose.yml
├─ 📁 caddy/
│  └ 📁 caddy/
│    └ 📄 Caddyfile
│  └ 📄 docker-compose.yml
...
plaintext

Let’s start with the easy part (the panel) first (read the instructions!):

Panel's Docker Compose
version: '3.8'
x-common:
  database:
    &db-environment
    # Do not remove the "&db-password" from the end of the line below, it is important
    # for Panel functionality.
    MYSQL_PASSWORD: &db-password "CHANGE_ME"
    MYSQL_ROOT_PASSWORD: "CHANGE_ME_TOO"
  panel:
    &panel-environment

    # !!!!!
    # CHANGE THIS
    APP_URL: "https://panel.kunet.dev"
    #
    # !!!!!

    # A list of valid timezones can be found here: http://php.net/manual/en/timezones.php
    APP_TIMEZONE: "UTC"
    APP_SERVICE_AUTHOR: "noreply@example.com"
  # Not strictly necessary unless you need to send mail.
  mail:
    &mail-environment
    MAIL_FROM: "noreply@example.com"
    MAIL_DRIVER: "smtp"
    MAIL_HOST: "mail"
    MAIL_PORT: "1025"
    MAIL_USERNAME: ""
    MAIL_PASSWORD: ""
    MAIL_ENCRYPTION: "true"

#
# ------------------------------------------------------------------------------------------
# DANGER ZONE BELOW
#
# The remainder of this file likely does not need to be changed. Please only make modifications
# below if you understand what you are doing.
#
services:
  database:
    image: mariadb:10.5
    restart: always
    networks:
      - caddy
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - "./pterodactyl/database:/var/lib/mysql"
    environment:
      <<: *db-environment
      MYSQL_DATABASE: "panel"
      MYSQL_USER: "pterodactyl"
  cache:
    image: redis:alpine
    restart: always
    networks:
      - caddy
  panel:
    networks:
      - caddy
    image: ghcr.io/pterodactyl/panel:latest
    restart: always
    links:
      - database
      - cache
    volumes:
      - "./pterodactyl/var/:/app/var/"
      - "./pterodactyl/nginx/:/etc/nginx/http.d/"
      - "./pterodactyl/certs/:/etc/letsencrypt/"
      - "./pterodactyl/logs/:/app/storage/logs"
    environment:
      <<: [*panel-environment, *mail-environment]
      DB_PASSWORD: *db-password
      APP_ENV: "production"
      APP_ENVIRONMENT_ONLY: "false"
      CACHE_DRIVER: "redis"
      SESSION_DRIVER: "redis"
      QUEUE_DRIVER: "redis"
      REDIS_HOST: "cache"
      DB_HOST: "database"
      DB_PORT: "3306"
networks:
  caddy:
    external: true
    name: caddy
    driver: bridge

Now:

Running it up (in the panel folder)
# magic to change the default database password to a secure random value
# (you won't need it for anything else unless you plan to reuse the database in which case you'd want to create a separate database compose file)
sed -i "s/\"CHANGE_ME\"/\"$(tr -dc A-Za-z0-9 < /dev/random | head -c 8)\"/" docker-compose.yml
sed -i "s/\"CHANGE_ME_TOO\"/\"$(tr -dc A-Za-z0-9 < /dev/random | head -c 8)\"/" docker-compose.yml

# create Caddy network
docker network create caddy --driver bridge --subnet 172.14.0.0/16

# now it's time to start it up
docker-compose up -d

# create a user for later
docker exec -it panel_panel_1 php artisan p:user:make

Now, let’s move on to wings:

docker-compose.yml
version: '3.8'
services:
  wings:
    image: ghcr.io/pterodactyl/wings:latest
    restart: always
    networks:
      - caddy
    ports:
      - "2022:2022"
    tty: true
    environment:
      TZ: "UTC"
      WINGS_UID: 988
      WINGS_GID: 988
      WINGS_USERNAME: pterodactyl
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
      - "/var/lib/docker/containers/:/var/lib/docker/containers/"
      - "/etc/pterodactyl/:/etc/pterodactyl/"
      - "/var/lib/pterodactyl/:/var/lib/pterodactyl/"
      - "/var/log/pterodactyl/:/var/log/pterodactyl/"
      - "/tmp/pterodactyl/:/tmp/pterodactyl/"
      - "/etc/ssl/certs:/etc/ssl/certs:ro"
networks:
  caddy:
    external: true
    name: caddy
    driver: bridge

Don’t start wings quite yet… we’ll get back to that soon enough.

Oh, and here’s the Caddy Docker Compose file:

Caddy's Docker Compose
version: '3.8'
services:
  caddy:
    container_name: caddy
    networks:
      - caddy
    image: caddy:alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
      - "8080:8080"
      - "8080:8080/udp"
    volumes:
      - ./caddy:/etc/caddy
      - ./caddy_data:/data
      - ./caddy_config:/config
    command: "sh -c 'caddy fmt -w /etc/caddy/Caddyfile && caddy run --config /etc/caddy/Caddyfile --adapter caddyfile'"
networks:
  caddy:
    external: true
    name: caddy
    driver: bridge

This is an especially cool Caddy config since it automatically formats the Caddyfile on boot!

File Structure
.
├─ 📁 panel/
│  └ 📄 docker-compose.yml
├─ 📁 wings/
│  └ 📄 docker-compose.yml
├─ 📁 caddy/
│  └ 📁 caddy/
│    └ 📄 Caddyfile
│  └ 📄 docker-compose.yml
...
plaintext

And inside the caddy folder which is in the same folder that the Caddy docker-compose.yml file is in is the Caddyfile (see file structure). Change the domains to the domains to your domains but keep the http://panel and http://wings:8080 as they are automatically correctly routed to the other containers through the magic of Docker’s DNS:

Caddyfile
panel.kunet.dev {
    reverse_proxy http://panel
}

node.kunet.dev:8080 {
    reverse_proxy http://wings:8080
}
Caddyfile

These commands on Linux will increase the maximum UDP buffer size, which you might want to do for performance reasons for QUIC (which runs on UDP) and is enabled by default in Caddy:

Increase UDP buffer size
# needs to be run as root
sysctl -w net.core.rmem_max=2500000
sysctl -w net.core.wmem_max=2500000

Go ahead and start up the Caddy proxy:

Start Caddy (in the caddy folder)
docker-compose up -d

Now log into the panel and begin the node creation process in the admin area.

You’ll want to make sure the following options are in order:

  • Behind Proxy (we are indeed behind a proxy)

Once it’s been created, head to the configuration tab and copy it all to clipboard.

You’ll want to place the contents of the file into the path /etc/pterodactyl/config.yml. After that, you’re free to start up wings.

Start Wings (in the wings folder)
docker-compose up -d

When you click on the side bar button for “Nodes” and see a green heart icon next to your new node, you’ll know that the configuration has correctly worked, and you can begin making servers to your heart’s content.

This guide is especially finnicky and difficult, ask questions in the comments or contact me directly.