Pterodactyl manages game servers through configuration files called Eggs. Each Egg defines everything the panel needs to run a specific application: which Docker container to use, how to start the software, which files to watch for logs, and what variables the user can configure through the web interface.
The default Egg library covers Minecraft, Rust, ARK, CS2, and a few dozen other common titles. The moment you want to host something not on that list, or a fork that behaves differently from the standard version, you need to write your own Egg.
This guide explains the full structure and walks through creating a working custom Egg from scratch.
Getting a Feel for the Format
Eggs are JSON files. Export any existing Egg from your panel to see the structure:
Go to Admin > Nests > Select a nest > Select an egg > Export.
Open the downloaded JSON in a text editor. You will see these top-level keys:
{
"_comment": "optional description",
"meta": { "version": "PTDL_v2", "update_url": null },
"exported_at": "...",
"name": "My Custom App",
"author": "[email protected]",
"description": "...",
"features": null,
"docker_images": { ... },
"file_denylist": [],
"startup": "...",
"config": { ... },
"scripts": { ... },
"variables": [ ... ]
}
The Docker Image
The docker_images key specifies which container image to run the application in. This is a key-value pair where the key is the display name shown in the panel and the value is the full image reference.
"docker_images": {
"Java 21": "ghcr.io/pterodactyl/yolks:java_21",
"Java 17": "ghcr.io/pterodactyl/yolks:java_17",
"Node 22": "ghcr.io/pterodactyl/yolks:nodejs_22"
}
The Pterodactyl team maintains a library of ready-made Docker images called "Yolks" at ghcr.io/pterodactyl/yolks. These images are stripped-down OS environments with common runtimes pre-installed. Use these when the runtime matches your application.
If you need something custom, you point to any accessible Docker image:
"docker_images": {
"Custom App": "docker.io/username/my-app-image:latest"
}
The image must be publicly accessible or you must configure Docker credential helpers on your Wing nodes for private registries.
The Startup Command
The startup key is the shell command that runs when a user presses the start button. You reference user-configurable variables using double curly braces:
"startup": "java -Xms{{SERVER_MEMORY}}M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}} --nogui"
{{SERVER_MEMORY}} and {{SERVER_JARFILE}} are variable placeholders. You define these variables in the variables array.
The startup command runs inside the Docker container as the user defined in the image. The working directory is the server's data volume.
Defining User Variables
Variables give end-users control over startup parameters through the panel's web interface, without them needing to edit configuration files or touch the console.
"variables": [
{
"name": "Server Jar File",
"description": "The name of the jar file to launch the server with.",
"env_variable": "SERVER_JARFILE",
"default_value": "server.jar",
"user_viewable": true,
"user_editable": true,
"rules": "required|string|max:20",
"field_type": "text"
},
{
"name": "Server Version",
"description": "The version to download and install.",
"env_variable": "MC_VERSION",
"default_value": "latest",
"user_viewable": true,
"user_editable": true,
"rules": "required|string|max:20",
"field_type": "text"
}
]
Key fields:
env_variable: The name used in{{CURLY_BRACE}}syntax in the startup command and install scriptuser_viewable: Whether the end-user sees this in their panel interfaceuser_editable: Whether they change itrules: Laravel validation rules. Userequired|string|max:20for text fields,required|integer|min:1|max:65535for port numbersfield_type:textfor text input,passwordfor hidden values
The Install Script
The scripts.installation section defines a script that runs once when the server is first created. Use this for downloading the application, running initial setup, or configuring default files.
"scripts": {
"installation": {
"script": "#!/bin/bash\napt-get update && apt-get install -y curl\ncurl -Lo server.jar https://example.com/download/{{MC_VERSION}}/server.jar\nif [ ! -f eula.txt ]; then\n echo 'eula=true' > eula.txt\nfi",
"container": "ghcr.io/pterodactyl/installers:debian",
"entrypoint": "bash"
}
}
The install script runs in a separate installer container. It has access to the server's data directory and to all defined environment variables. The ghcr.io/pterodactyl/installers:debian image has common utilities pre-installed.
Write install scripts defensively. Check if files already exist before downloading them. A reinstall should not corrupt an existing server.
Config File Parsing
Pterodactyl reads specific configuration files to extract values like the bound port and log file location. Define these in the config section:
"config": {
"files": {
"server.properties": {
"parser": "properties",
"find": {
"server-port": "{{server.build.default.port}}"
}
}
},
"startup": {
"done": "Done ({{startup_time}}s)! For help, type",
"userInteraction": []
},
"stop": "stop",
"logs": {
"custom": false,
"location": "logs/latest.log"
}
}
config.startup.done is a string that Wings watches for in the server output. When it appears, Wings marks the server as running in the panel. Set this to a string that only appears in your application's output when it has finished starting.
config.stop is the console command sent to the server when the stop button is pressed. Most Java servers use stop. Other applications might use exit, quit, or a SIGTERM signal.
Importing the Egg Into Your Panel
Save your finished JSON as egg-myapp.json. In the Pterodactyl admin panel:
- Go to Admin > Nests
- Create a new Nest for organizational grouping, or use an existing one
- Click Import Egg and upload your JSON file
After importing, create an Allocation and then a Server using the new Egg to test it. Watch the Wings log during installation and first startup to catch any script errors.
Testing and Iteration
Egg development is iterative. Errors appear in Wings logs and in the server creation output within the panel.
Common issues:
- Install script fails to download files: check that the URL resolves from inside a Docker container. Some download links require specific user agents or headers.
- Server does not show as "Running": your
startup.donestring does not match the actual output. Run the server manually and look at the exact startup message. - Variables not substituting: check the variable name in the
variablesarray matches exactly what you use in{{DOUBLE_BRACES}}. Case matters.
The community maintains a large collection of pre-built Eggs at github.com/pelican-eggs/eggs. Check there before writing from scratch; someone has likely already created an Egg for your target application.