A Pterodactyl egg for Python apps and Discord bots, on Python 3.11, 3.12, 3.13, 3.14 and 3.15.
- Fast installs:
requirements.txtis installed with uv into a venv (falls back to pip if uv can't), and skipped entirely on restarts when nothing changed - Multi-Bot Mode: host many bots on one server, each in its own folder with its own
packages and
.env, and control them from a web panel on the server's port - Upload or deploy from Git: upload your files, or clone a Git repo (public or private) and optionally pull it on every start
- Ready for Discord: ffmpeg, Opus and libsodium are in the image for voice and music bots, plus a compiler for packages without a prebuilt wheel
- Update check: the console says when the egg or the Docker image has an update
- Maintained images: Debian based,
amd64andarm64, rebuilt weekly for security fixes
- Pterodactyl Panel 1.x with Wings (the egg is in the
PTDL_v2format) - An
amd64orarm64node - Internet access from the node, for the Docker image, Python packages and your Git repo
- Download
egg-python.jsonfrom the latest release. - In the panel: Admin → Nests → Import Egg, upload it into the nest you want.
- Create a server with the egg and pick a Python version under Docker Image.
Set Multi-Bot Mode to
1here if the server will host several bots. - Upload your bot (or set a Git repo) and start the server.
With Multi-Bot Mode off the server runs one Python app, like a plain Python egg:
- Upload your files: the Entry File (
bot.pyby default) and arequirements.txt. - Start the server. The first start creates
.venv/and installs your requirements; later starts skip that unlessrequirements.txt, Extra Packages or the Python version changed.
Need something other than python bot.py? Put it in Custom Startup Command, e.g.
gunicorn app:app -b 0.0.0.0:$SERVER_PORT. It runs inside the venv, so pip and any
installed tools are on the path.
Turn on Multi-Bot Mode when creating the server. Only an admin can change it later (Admin → Servers → server → Startup), so a server's users can't switch it.
Each bot gets its own folder:
/home/container
├── welcomer/
│ ├── bot.py # bot.py, main.py or app.py
│ ├── requirements.txt # this bot's packages, in its own .venv
│ └── .env # this bot's tokens and settings
├── radio/
│ ├── bot.py
│ ├── requirements.txt
│ ├── .env
│ └── .disabled # optional: don't start this bot automatically
└── logs/ # one log file per bot
- Add a bot: create a folder with a
bot.pyin it. The panel finds it within 10 seconds (or press Rescan), then start it. - Separate packages: each bot has its own venv, so one bot can pin
discord.py==2.3.2while another needsdiscord.py>=2.6. uv shares downloads between them, so this costs little disk space. - Separate secrets: each bot only sees its own
.env. The panel password and Git token are never passed to bots. - Crash recovery: a bot that crashes is restarted after 5 seconds, then 10, 20, ... up to 5 minutes between tries. A bot that exits cleanly (exit code 0) stays stopped.
- Stopping: bots get Ctrl+C first, so they can log out of Discord cleanly, and are only killed if they don't stop within 8 seconds. Stopping the server stops every bot.
Open http://<node-ip>:<port> (the server's primary allocation). From there you can:
- see each bot's state, uptime, CPU and memory, and the server's total memory use
- start, stop and restart a bot, or every bot at once
- watch a bot's live output
- turn auto-start on or off, or force a fresh dependency install
The password is the Bot Panel Password variable. If that's blank, one is generated and
shown in the console on every start (it's also in .rustix/panel-password). Logins are
limited to 10 wrong tries per 5 minutes. The panel speaks plain HTTP: put a reverse proxy or
Cloudflare in front of the port if you open it to the internet.
The server console also controls the bots. Type help for the list:
| Command | What it does |
|---|---|
list |
Every bot with its state, CPU and memory |
start <bot>, stop <bot>, restart <bot> |
Use all for every bot |
enable <bot>, disable <bot> |
Turn auto-start on or off |
reinstall <bot> |
Force a fresh dependency install |
logs <bot> [lines] |
Print a bot's recent output |
rescan |
Look for new bot folders now |
password |
Show the generated web panel password |
| Variable | Default | What it does |
|---|---|---|
Multi-Bot Mode (MULTI_BOT) |
0 |
Host several bots with a web panel. Admin only |
Entry File (PY_FILE) |
bot.py |
File to run (single app) |
Requirements File (REQUIREMENTS_FILE) |
requirements.txt |
Installed on start when it changes (single app) |
Auto Install Dependencies (AUTO_INSTALL) |
1 |
Install requirements and extra packages on start |
Extra Packages (PY_PACKAGES) |
Space separated packages on top of the requirements file (single app) | |
Custom Startup Command (CUSTOM_CMD) |
Run this instead of the Entry File (single app) | |
Bot Panel Password (PANEL_PASSWORD) |
Web panel password; blank generates one | |
Bots Folder (BOTS_DIR) |
Folder holding the bot folders; blank is the server's main folder | |
Auto Restart Bots (AUTO_RESTART) |
1 |
Restart crashed bots with backoff |
Bot Logs In Main Console (CONSOLE_MIRROR) |
0 |
Also show every bot's output in the main console, not only in its own log in the panel |
Update Check (UPDATE_CHECK) |
1 |
Say in the console when the egg or the Docker image has an update |
| Variable | Default | What it does |
|---|---|---|
Git Repo Address (GIT_ADDRESS) |
Repo to clone on install, e.g. https://github.com/you/your-bot |
|
Git Branch (BRANCH) |
Branch to clone; blank uses the repo's default branch | |
Auto Update (AUTO_UPDATE) |
0 |
git pull on every start |
User Uploaded Files (USER_UPLOAD) |
0 |
Skip the Git setup, you upload the files yourself |
Git Username (USERNAME) |
For private repos | |
Git Access Token (ACCESS_TOKEN) |
For private repos; use a personal access token, not your password |
The repo is only cloned into an empty server. If the server is already a clone of the same repo, a reinstall pulls it; any other files are left alone. In Multi-Bot Mode the repo holds the bot folders.
The images are ghcr.io/rustix-development/pterodactyl-python-egg:python3.11 up to :python3.15,
built from docker/Dockerfile on the official python:*-slim images with
uv, Git, ffmpeg, Opus, libsodium, build tools, tini and the bot manager
(docker/rustix/).
The start script and bot manager live in the image, not on the server, so restarting a server picks up their updates without a reinstall.
| Path | What it's for |
|---|---|
.venv/ |
The app's packages (single app). Delete it to rebuild from scratch |
<bot>/.venv/ |
Each bot's packages (Multi-Bot Mode) |
logs/ |
One log per bot (Multi-Bot Mode). A log over 5 MB is moved to *.log.1 |
.cache/ |
uv and pip download cache, shared by every venv |
.rustix/ |
Web panel session key and generated password |
- Import this egg, then Admin → Servers → server → Startup: change the egg to this one,
pick a Docker Image, and set the startup command to
rustix-start. - If the old Startup Command 2 was something other than
python bot.py, set Entry File or Custom Startup Command to match. - For a server that ran the old
panel.pywith bot folders next to it, set Multi-Bot Mode to1. The folders,.envfiles and.disabledmarkers work as they are, and aPANEL_PASSWORDin the root.envis still used if the variable is blank.panel.pyand the rootrequirements.txtaren't needed any more.
Every time a server starts it checks for updates in the background and says in the console if:
- the egg has an update: download
egg-python.jsonfrom the latest release, then Admin → Nests → (nest) → Python → Update Egg with it (not Import Egg, that makes a second copy). - the Docker image has an update: restart the server, the panel downloads the latest image
on every start. If it keeps showing, run
docker pull ghcr.io/rustix-development/pterodactyl-python-egg:python3.xon the node to see why. - the server's Python version is no longer offered: Admin → Servers → server → Startup → Docker Image, pick one of the egg's images.
Turn it off per server with the Update Check variable.
docker build --build-arg PYTHON_VERSION=3.13 -t pterodactyl-python-egg:python3.13 docker/The Build images workflow builds every version for
amd64 and arm64 and pushes them to GHCR on every change to docker/ and weekly.
Can't find bot.py: upload it, or set Entry File to your file's name.- A package fails to install: the console shows the error. Brand-new Python versions (3.15) may not have prebuilt packages yet; pick an older Python under Docker Image.
- A bot keeps restarting: open its logs in the panel (or
logs <bot>in the console); the error is just aboveexited with code. - Can't reach the web panel: it listens on the server's primary allocation, and only in
Multi-Bot Mode. The console says
Launching web panel on port ...once it's up. - Git clone fails: check the address, branch and, for a private repo, the username and access token. The clone only runs into an empty server.
Found a bug or want a feature? Open an issue.
MIT, see LICENSE. Git install logic from parkervcp/eggs; see NOTICE.md for credits and their licenses.