A Pterodactyl egg is one JSON file that tells the panel how to install, start, configure and stop one kind of server. It names the Docker images the server can run in, holds the startup command with {{VARIABLES}} in it, defines those variables with defaults and validation rules, lists which config files to rewrite at boot, carries a bash install script that runs once in a throwaway container, and says which log line means "started" and which command means "stop". Everything you see on a panel's Startup tab is an egg rendered as a form. Knowing how one is put together explains why some edits stick and others revert, why a server sits on "starting" forever, and why a Stop button saves the world in one game and not another.
If you have not read it, Pterodactyl panel explained covers the panel, the Wings daemon and the one-container-per-server model. This post opens the egg itself.
Where eggs come from#
Pterodactyl ships a small set of eggs - Minecraft variants, a few Source games, Rust, a voice server or two - grouped into nests. Almost every other game egg in use comes from the community collection on GitHub, maintained for years under parkervcp/eggs and now under the pelican-eggs organisation, with one folder per game. Hosts import those, modify them, or write their own.
An egg is exported and imported as JSON in a format the panel labels PTDL_v2. Pelican, the Pterodactyl fork, has its own revision of the format and can import most Pterodactyl eggs. Whatever the source, read an egg's install script before importing it: it runs with network access and writes into the server's files, and an egg is code you did not write in the same way a plugin is.
The anatomy of an egg#
Trimmed to the parts that matter, a Minecraft-style egg looks like this:
{ "meta": { "version": "PTDL_v2", "update_url": null }, "name": "Paper", "features": ["eula", "java_version"], "docker_images": { "Java 21": "ghcr.io/pterodactyl/yolks:java_21", "Java 17": "ghcr.io/pterodactyl/yolks:java_17" }, "file_denylist": [], "startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}", "config": { "files": "{\"server.properties\":{\"parser\":\"properties\",\"find\":{\"server-ip\":\"0.0.0.0\",\"server-port\":\"{{server.build.default.port}}\"}}}", "startup": "{\"done\":\")! For help, type \"}", "logs": "{}", "stop": "stop" }, "scripts": { "installation": { "container": "ghcr.io/pterodactyl/installers:alpine", "entrypoint": "ash", "script": "#!/bin/ash\ncd /mnt/server\n..." } }, "variables": [ { "name": "Server Jar File", "env_variable": "SERVER_JARFILE", "default_value": "server.jar", "user_viewable": true, "user_editable": true, "rules": "required|regex:/^([\\w\\d._-]+)(\\.jar)$/", "field_type": "text" } ]}The config values are JSON encoded inside strings, which is why they are full of escaped quotes. That is ugly and easy to break by hand; edit them in the panel's admin interface or with care.
| Field | What it controls |
|---|---|
docker_images | The runtime images a server may use, label to image |
startup | The launch command, with variables substituted |
variables | The Startup tab fields, their defaults and validation |
config.files | Which config files Wings rewrites before each start |
config.startup | The log text that marks the server as running |
config.stop | How Wings asks the server to stop |
scripts.installation | The one-time install script and the image it runs in |
file_denylist | Files users may not open or edit in the file manager |
features | Panel helpers, such as the EULA prompt and Java version switcher |
Docker images: the runtime, not the game#
The image is the environment the game runs in: the operating system libraries, the Java or .NET or Wine version, and an entrypoint script. It does not contain the game. The game files live on the server's volume, mounted at /home/container, and they survive image changes.
The images most eggs use are the "yolks" images: ghcr.io/pterodactyl/yolks for the official set and ghcr.io/parkervcp/yolks and ghcr.io/parkervcp/steamcmd for the community set. Tags name the runtime - java_21, debian, dotnet_8, Wine tags for Windows-only games, and so on. Tag names and contents change over time, so check the repository rather than copying a tag from an old egg.
Every yolks image follows the same pattern. The container runs as an unprivileged user named container, starts in /home/container, and runs an entrypoint that takes the STARTUP environment variable, converts {{VAR}} into ${VAR}, and evaluates it:
cd /home/containerMODIFIED_STARTUP=$(echo -e ${STARTUP} | sed -e 's/{{/${/g' -e 's/}}/}/g')echo ":/home/container$ ${MODIFIED_STARTUP}"eval ${MODIFIED_STARTUP}That echoed line is the first thing you see in the console on every start, and it is the fastest way to check what was actually launched. If a variable is empty, you will see it missing from that line.
Choosing the wrong image is a common failure. A Minecraft version that needs Java 21 started in a Java 17 image fails with UnsupportedClassVersionError; the Minecraft JVM flags and Java versions post has the version table.
The startup line and variables#
The startup field is a template. Wings provides some values itself, from the server's build:
| Variable | Source |
|---|---|
SERVER_MEMORY | The memory limit in MB |
SERVER_IP | The primary allocation's IP |
SERVER_PORT | The primary allocation's port |
P_SERVER_UUID | The server's ID |
P_SERVER_LOCATION | The node's location name |
TZ | The time zone configured for Wings |
Everything else comes from the egg's variables array, and each variable becomes an environment variable inside the container as well as a substitution in the startup line. That is why a variable can be used in both places: in startup as {{MAX_PLAYERS}}, and in a shell script or the game itself as $MAX_PLAYERS.
Each variable carries:
env_variable- the name used in the startup line and the environment.default_value- what a new server gets.user_viewableanduser_editable- whether a customer sees it on the Startup tab, and whether they can change it. Hosts hide variables that hold their own secrets and lock ones that would break the plan, such as a memory override.rules- Laravel validation rules, the same syntax the panel uses everywhere:required|string|max:32,nullable|string,required|integer|between:1,100,required|in:true,false,regex:/.../. A value that fails the rule is refused when you save the field.
A well-written egg uses rules to prevent mistakes that would otherwise crash at boot, such as a non-numeric port or a jar file name without .jar. A poorly written one accepts anything, and the error appears in the console instead.
Changes to variables take effect on the next start, because they only change the environment and the startup line. Variables that the install script uses - a game version, a branch, a modpack ID - only take effect when the server is reinstalled, because the install script is the only thing that reads them. That distinction explains most "I changed the version and nothing happened" reports.
Config parsers: why some edits revert#
config.files tells Wings to open specific files before every start and set specific keys. The parser is one of properties, ini, json, yaml, xml or file (plain text, matched by line prefix), and find lists the keys and the values to force.
The common use is ports and addresses. The Minecraft egg above forces server-port to {{server.build.default.port}} - the server's primary allocation - and server-ip to 0.0.0.0, so the game always binds where the panel says. Proxy eggs set nested YAML keys with a dotted path such as listeners[0].host. Other eggs set query ports, RCON ports or the maximum player count from variables.
The consequence is the single most confusing thing about panel hosting: if you edit a key that the egg manages, your edit is overwritten on the next start, silently. The file looks like it reverted itself. The fix is to change the value where the egg takes it from - the Startup tab variable, or the port on the Network tab - not in the file. If a setting keeps reverting and you cannot find a variable for it, the egg is forcing a fixed value, and on a hosted panel that is a question for the host. The wider list of reasons a config edit does not stick is in game server config file formats.
The install script#
Installation is a separate container from running. When a server is created or reinstalled, Wings:
- Pulls the image named in
scripts.installation.container(an installer image withcurl,jq,git,unzipand similar tools). - Mounts the server's volume at
/mnt/server. - Passes the egg's variables in as environment variables.
- Runs the script with the named
entrypoint(bashorash). - Throws the container away, and only then allows the server to start in its runtime image.
A typical SteamCMD-based install script downloads SteamCMD into /mnt/server/steamcmd, runs app_update with the egg's app ID and branch variables, copies the Steam client libraries the game expects into .steam/sdk64 or .steam/sdk32, and writes a default config if none exists. A Minecraft script calls a version API and downloads the jar. The details of the SteamCMD half are in SteamCMD app IDs and beta branches.
Two properties of install scripts matter to customers:
- Reinstalling runs the script again. Good scripts only download and update the game, leaving worlds and configs alone. Careless ones overwrite configs with defaults. Take a backup before reinstalling a server you care about.
- The install log is separate. If a new server never starts, the install probably failed. The panel shows the installer's output in the console during installation, and Wings keeps the install log on the node.
Done detection and the stop command#
config.startup.done is a string (or a list of strings) that Wings watches for in the console. When it appears, the server's state changes from "starting" to "running". For Minecraft it is )! For help, type , matching the line Paper prints when it has finished loading. For Valheim it is typically the line about the game server being connected. If the game's output changes in an update and no longer contains that text, the server works perfectly but the panel shows "starting" forever. That is cosmetic for players and annoying for schedules that wait on a running state.
config.stop is how Wings asks for a stop. It is either a console command written to the server's input - stop for Minecraft, quit for Source games - or ^C, which sends SIGINT to the process. If the server does not exit within Wings' timeout, it is killed.
This is where worlds are won or lost. An egg whose stop command triggers a save and a clean exit gives you a safe Stop and Restart button. An egg that sends ^C to a game that ignores it, or that exits without saving on that signal, gives you a button that behaves like a kill. Test it once on any game you care about: change something, stop, start, check. The restart schedules that help post explains why the difference matters, and game server autosave intervals how to limit the damage when a stop is not clean.
Writing or changing your own egg#
If you run your own panel and want an egg for a game nobody has packaged:
- Start from the closest existing egg. A SteamCMD game egg for a similar engine gets you 80 per cent of the way.
- Get the install working by hand first. Run the installer image locally with Docker, mount an empty folder at
/mnt/server, and run your script until it produces a working install. - Get the startup line working in the runtime image. Same image, same folder mounted at
/home/container, run the command with real values. - Add variables for anything a user should change, with rules that refuse bad values.
- Add config parsers only for keys the panel must own - ports, bind address. Everything else should be the user's to edit.
- Set a done string that appears on every successful start, and a stop command you have tested saves the world.
On a hosted panel you usually cannot import eggs; the host maintains them. On RE:NODE the host side maintains the eggs, and their variables appear on the Startup tab, alongside environment variables, with the ports on the Network tab. Admin, RCON and database passwords are generated per server at install, Minecraft comes up on Paper with the matching Java version and the EULA accepted, and games that need your own Steam login or licence key wait on the Setup tab until you supply it. If you need a stack no egg covers, a dedicated server gives you the whole machine.
Troubleshooting eggs#
The server shows "starting" forever but players can join. The done string no longer matches the game's output. Cosmetic, but tell the host or fix the egg.
A setting in a config file keeps reverting. The egg's config parser owns that key. Change it on the Startup or Network tab.
I changed the version variable and nothing changed. The version is read by the install script. Reinstall, after taking a backup.
`exec format error` or `not found` on a binary that is clearly there. The image does not match the game: a 64-bit binary in an image missing its libraries, or a Windows .exe started without Wine.
The console's first line shows an empty value. A variable is blank. Check the Startup tab for a required field with no default.
FAQ#
What is an egg in Pterodactyl?
A JSON definition of one kind of server: which Docker images it runs in, how it is installed, its startup command and variables, which config keys the panel controls, and how to tell that it started and how to stop it. Every server on the panel is created from one.
What is the difference between a nest and an egg?
A nest is a group of eggs, such as all Minecraft variants or all Source games. The egg is the actual definition. Nests are organisational and change nothing about how a server runs.
Why does changing a Startup variable need a reinstall sometimes?
Some variables are only read by the install script - a game version, a branch, a modpack. The running server never sees them. Variables used in the startup line or read by the game take effect on the next restart.
Can I use eggs from the community repository on any panel?
On a panel you administer, yes - import the JSON in the admin area. On a hosted panel you normally cannot add eggs yourself. Check the install script of any egg before importing it, since it runs with network access.
Does changing the Docker image delete my files?
No. The image is only the runtime. Server files live on the volume mounted at /home/container and stay in place; only the libraries and tools around them change.




Comments
Completely anonymous: no account, no email, no cookie. We store the name you type, the text and the time - nothing else. Links are limited and markup is not rendered.