Table of Contents
- ✅ Egg Creation Checklist & Best Practices
- 📋 Pre-Creation Checklist
- 🏗️ Structure & Metadata Checklist
- 🐳 Docker Image Checklist
- ⚙️ Variables Checklist
- 🚀 Startup Command Checklist
- 📝 Installation Script Checklist
- 🔧 Configuration Files Checklist
- 🎯 Startup Detection Checklist
- 🔗 Links & Organization Checklist
- 📚 Documentation Checklist
- 🧪 Testing Checklist
- 🚨 Common Mistakes to Avoid
- ❌ Hardcoding Values
- ❌ Wrong Variable Format in startup vs config.files
- ❌ Wrong field_type Value
- ❌ Missing user_viewable / user_editable
- ❌ Typos in Variable Names
- ❌ No Description
- ❌ Invalid JSON
- ❌ Wrong Parser Type
- ❌ Wrong Install Path
- ❌ Not Testing
- ❌ No Error Handling
- 🎯 Before You Share
- 📊 Quality Scoring
- 🔄 Egg Creation Workflow
- 🎓 Learning Resources
- 💡 Pro Tips
- 🎉 You're Ready!
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
✅ Egg Creation Checklist & Best Practices
This is your complete checklist for creating professional, working eggs. Use this as a final review before sharing your egg with the community!
📋 Pre-Creation Checklist
Before you start building your egg, make sure you have:
- ✅ A text editor (VS Code, Notepad++, or Notepad)
- ✅ An existing egg to reference (optional but helpful)
- ✅ Knowledge of what you're creating (game, bot, app)
- ✅ Understanding of the Egg Basics
- ✅ List of variables you want users to customize
🏗️ Structure & Metadata Checklist
Required Fields
- ✅
"meta": { "version": "PTDL_v2" }- Always required - ✅
"name"- Clear, descriptive egg name - ✅
"author"- Your name or email - ✅
"description"- What does this egg do? - ✅
"docker_images"- At least one valid image - ✅
"file_denylist"- Usually[](empty array)
Good Practices
- ✅ Name is concise (under 50 characters)
- ✅ Description explains what the egg does
- ✅ Author information is included
- ✅ Docker images are actual, working images
- ✅ No typos in metadata
- ✅ Description mentions any requirements (games, knowledge, etc.)
Example:
{
"name": "Counter-Strike 2",
"author": "admin@example.com",
"description": "A Counter-Strike 2 game server using SteamCMD",
"meta": {
"version": "PTDL_v2"
}
}
🐳 Docker Image Checklist
- ✅ Chosen appropriate base image (ubuntu, nodejs, java, steamcmd, etc.)
- ✅ Image URL uses the correct registry (
ghcr.io/ptero-eggs/yolks:*for runtime) - ✅ Installation container uses
ghcr.io/ptero-eggs/installers:debianor:alpine - ✅ Image has all required software pre-installed
- ✅ Provided multiple image options if applicable
- ✅ Image documentation checked for any special requirements
Common Runtime Images:
- Ubuntu:
ghcr.io/ptero-eggs/yolks:ubuntu - Debian:
ghcr.io/ptero-eggs/yolks:debian - Node.js:
ghcr.io/ptero-eggs/yolks:nodejs_20 - Java:
ghcr.io/ptero-eggs/yolks:java_21 - Python:
ghcr.io/ptero-eggs/yolks:python_3.12 - SteamCMD:
ghcr.io/ptero-eggs/steamcmd:sniper
Common Installer Containers:
ghcr.io/ptero-eggs/installers:debian(use withbash)ghcr.io/ptero-eggs/installers:alpine(use withash)
⚙️ Variables Checklist
For Each Variable, Include ALL of These:
- ✅
"name"- Human-readable name (e.g., "Server Name") - ✅
"description"- Clear explanation of what it does - ✅
"env_variable"- UPPERCASE with underscores (e.g., "SERVER_NAME") - ✅
"default_value"- Sensible default that works (always a string, even for numbers:"20"not20) - ✅
"user_viewable"- Whether the user can see this variable (trueorfalse) - ✅
"user_editable"- Whether the user can change this variable (trueorfalse) - ✅
"rules"- Validation rules (e.g.,"required|string|max:64") - ✅
"field_type"- Always"text"— no exceptions!
Important —
field_typeis ALWAYS"text": There are no other valid values. You do not use"number","password","boolean", or anything else. Instead, you express the type of data through therulesfield:
- Numbers:
"rules": "required|numeric|between:1,64"- Booleans:
"rules": "required|boolean"(use"0"/"1"as the default_value)- Optional strings:
"rules": "nullable|string|max:64"- Required strings:
"rules": "required|string|max:64"- URLs:
"rules": "required|url"
Variable Best Practices:
- ✅ Use UPPERCASE for variable names
- ✅ Use underscores between words (SNAKE_CASE)
- ✅ Descriptions tell users exactly what to enter
- ✅ Default values actually work out of the box
- ✅ No hardcoded values in startup/config
- ✅ Related variables grouped together
- ✅ Port numbers validated between 1024–65535
- ✅ String lengths limited with
max:
Variable Validation Examples:
- ✅ Port numbers:
"required|numeric|between:1024,65535" - ✅ Boolean flags:
"required|boolean"with default"0"or"1" - ✅ Optional strings:
"nullable|string|max:64" - ✅ Required fields: start with
required| - ✅ Numeric ranges: use
between:min,max - ✅ String lengths: use
max:N
Complete variable example:
{
"variables": [
{
"name": "Server Port",
"description": "The port your server will run on (1024-65535)",
"env_variable": "SERVER_PORT",
"default_value": "25565",
"user_viewable": true,
"user_editable": true,
"rules": "required|numeric|between:1024,65535",
"field_type": "text"
},
{
"name": "Max Players",
"description": "Maximum number of players allowed (1-100)",
"env_variable": "MAX_PLAYERS",
"default_value": "20",
"user_viewable": true,
"user_editable": true,
"rules": "required|numeric|between:1,100",
"field_type": "text"
}
]
}
🚀 Startup Command Checklist
- ✅ Startup command is complete and valid
- ✅ Variables use correct format:
{{VARIABLE_NAME}}(in the startup field) - ✅ No hardcoded values (use variables instead)
- ✅ Path separators correct for Linux (forward slashes)
- ✅ Command tested locally if possible
- ✅ Port uses
{{server.build.default.port}}or a custom variable like{{SERVER_PORT}} - ✅ No typos in command syntax
- ✅ Proper escaping for special characters
- ✅ Runtime files are referenced as
/home/container/...(not/mnt/server/...)
Good Example:
{
"startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}"
}
Bad Example:
{
"startup": "java -Xmx2048M -jar server.jar --port 25565 --name MyServer"
}
(Don't hardcode values — use variables instead!)
📝 Installation Script Checklist
- ✅ Script is valid bash (or ash) syntax
- ✅ Uses the correct shebang (
#!/bin/bashfor debian,#!/bin/ashfor alpine) - ✅ Comments explain what each section does
- ✅ Creates
/mnt/serverdirectory before writing files - ✅ Downloads required files correctly into
/mnt/server - ✅ Sets proper file permissions
- ✅ Handles errors gracefully
- ✅ Variables are used (not hardcoded)
- ✅ No interactive prompts (unattended install)
- ✅ Prints status messages for user feedback
- ✅ Creates default config files if needed
- ✅ Tested and works from scratch
Path reminder: The installation script runs in its own container. Server files must go into
/mnt/server. At runtime, the server container sees those same files at/home/container.
Script Best Practices:
- ✅ Use meaningful variable names
- ✅ Include error checking (
if [ $? -eq 0 ]) - ✅ Check if files already exist before overwriting
- ✅ Handle missing dependencies
- ✅ Provide clear status messages
- ✅ Clean up temporary files
- ✅ Don't use
sudo(run as container user)
Example:
#!/bin/bash
echo "Installing MyApp..."
# Create server directory
mkdir -p /mnt/server
cd /mnt/server
# Download the application
wget https://example.com/app-latest.tar.gz
if [ $? -eq 0 ]; then
echo "Download successful"
tar -xzf app-latest.tar.gz
rm app-latest.tar.gz
else
echo "Error: Download failed"
exit 1
fi
echo "Installation complete!"
🔧 Configuration Files Checklist
File Management:
- ✅ Parser type matches file format (
json,yaml,ini,properties,file) - ✅ File path is correct and matches where the file lives
- ✅ File exists or is created during installation
- ✅ Search strings are exact (case-sensitive!)
- ✅ Replacement values use the correct variable format
- ✅ In
config.filesparsers, variables use{{server.build.env.VARIABLE_NAME}} - ✅ In
config.filesparsers, the port uses{{server.build.default.port}} - ✅ Configuration file is valid JSON/YAML/etc. after modification
For Each Parser:
JSON:
- ✅ Using dot notation for nested values (e.g.,
"database.host") - ✅ Quotes balanced properly
- ✅ Valid JSON after modification
YAML:
- ✅ Using spaces only (no tabs!)
- ✅ Indentation consistent
- ✅ Colon spacing correct (
key: value)
INI:
- ✅ Section names in brackets
[section] - ✅ Using dot notation with section:
section.key - ✅ Key=value format correct
Properties:
- ✅ Simple key=value pairs
- ✅ No sections
- ✅ Keys match exactly
File (plain text):
- ✅ Search string is exact and unique
- ✅ Entire line being replaced correctly
- ✅ No ambiguous search patterns
Example (decoded config.files value):
{
"config.json": {
"parser": "json",
"find": {
"server.port": "{{server.build.default.port}}",
"server.name": "{{server.build.env.SERVER_NAME}}"
}
}
}
🎯 Startup Detection Checklist
- ✅ Detection string appears in server output when it's ready
- ✅ Detection string is unique (doesn't appear earlier during startup)
- ✅ Detection string appears shortly after the server is ready
- ✅
config.startupis a JSON-encoded string (not a raw object) - ✅ Format is correct:
"{\"done\": \"Server is ready\"}"
Example (as it looks inside the egg JSON):
{
"config": {
"startup": "{\"done\": \"Server listening on port\"}",
"files": "{}",
"logs": "{}",
"stop": "^C"
}
}
🔗 Links & Organization Checklist
- ✅ All JSON is properly formatted
- ✅ No broken references between sections
- ✅ File paths use correct separators (/ not \)
- ✅ All variables referenced in startup or config.files are defined in the variables array
- ✅ No circular dependencies
📚 Documentation Checklist
https://git.flety.net/damien/pterodactyl-eggs/wiki/Home:
- ✅ Explains what the egg does
- ✅ Lists available settings and what they do
- ✅ Shows how to use the egg
- ✅ Mentions any special requirements
- ✅ Includes examples if applicable
- ✅ Clear instructions for first-time users
- ✅ Links to official documentation if needed
Example README structure:
# My Game Server
## What This Egg Does
Brief description of the game/app.
## Requirements
- Minimum RAM: 2GB
- Recommended RAM: 4GB
- Disk space: 10GB
## Configuration
List of settings users can adjust:
- **Server Name**: Display name in server browser
- **Max Players**: Maximum concurrent players
- **Port**: Server port (default: 25565)
## Support
Where to get help if something breaks.
🧪 Testing Checklist
Before releasing your egg, test:
Installation:
- ✅ Install from scratch completes successfully
- ✅ All required files are downloaded correctly into
/mnt/server - ✅ Permissions are set properly
- ✅ Installation doesn't take too long (< 5 minutes normally)
- ✅ Disk space requirements are reasonable
- ✅ No errors in installation log
Startup:
- ✅ Server starts successfully
- ✅ Startup detection string appears in console output
- ✅ Server is actually running (not just starting)
- ✅ No errors in console output
- ✅ Takes reasonable time to start (< 30 seconds normally)
- ✅ Port is accessible from outside the container
Variables:
- ✅ Changing a variable actually affects behavior
- ✅ Default values work as-is without any changes
- ✅ Invalid values are caught by validation rules
- ✅ Special characters handled correctly
- ✅ Port values actually change the port used
Configuration Files:
- ✅ Configuration files are created/modified correctly at
/home/container - ✅ Files are valid format after modification
- ✅ Variables substituted properly
- ✅ No leftover template syntax in files
- ✅ File permissions allow the server to read/write
Edge Cases:
- ✅ Install after already installed (if applicable)
- ✅ Very long server names (near the max length)
- ✅ Port numbers at edges (1024, 65535)
- ✅ Zero values where numeric
- ✅ Empty strings where optional
- ✅ Unicode characters in names
🚨 Common Mistakes to Avoid
❌ Hardcoding Values
Bad: "startup": "java -Xmx2048M -jar server.jar --port 25565"
Good: "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}"
❌ Wrong Variable Format in startup vs config.files
In startup command: {{VARIABLE_NAME}}
In config.files parser: {{server.build.env.VARIABLE_NAME}}
Mixing these up means your variables simply won't work!
❌ Wrong field_type Value
Bad: "field_type": "number" or "field_type": "password" or "field_type": "boolean"
Good: "field_type": "text" — it is always "text", no exceptions. Use rules to express the data type.
❌ Missing user_viewable / user_editable
Bad:
{
"name": "Server Port",
"env_variable": "SERVER_PORT",
"default_value": "25565",
"rules": "required|numeric|between:1024,65535",
"field_type": "text"
}
Good:
{
"name": "Server Port",
"description": "The port your server listens on",
"env_variable": "SERVER_PORT",
"default_value": "25565",
"user_viewable": true,
"user_editable": true,
"rules": "required|numeric|between:1024,65535",
"field_type": "text"
}
❌ Typos in Variable Names
Bad: "env_variable": "SERVER_NAME" but using {{SERVER_NAEM}} in startup
Good: Spell consistently and double-check every reference!
❌ No Description
Bad: "description": ""
Good: "description": "Your Discord bot token from the Developer Portal"
❌ Invalid JSON
Test your JSON with a JSON validator before submitting!
❌ Wrong Parser Type
Bad: "parser": "json" for a YAML file
Good: "parser": "yaml" for YAML files
❌ Wrong Install Path
Bad: Writing files to /home/container inside the installation script
Good: Installation script always writes to /mnt/server
❌ Not Testing
Bad: Creating an egg and never testing it Good: Test thoroughly before sharing — at least one full fresh install!
❌ No Error Handling
Bad: Installation script that fails silently Good: Script checks for errors and reports them clearly
🎯 Before You Share
Final checklist before releasing your egg:
- ✅ Metadata is complete and accurate
- ✅ All variables include every required field (
name,description,env_variable,default_value,user_viewable,user_editable,rules,field_type) - ✅ Every
field_typeis"text" - ✅ Installation script writes to
/mnt/serverand works from scratch - ✅ Startup command works properly and uses
{{VARIABLE_NAME}}syntax - ✅ Configuration files are managed correctly using
{{server.build.env.VARIABLE_NAME}} - ✅ Tested on fresh installation
- ✅ Tested changing each variable
- ✅ Tested edge cases (long names, special chars)
- ✅ No hardcoded values anywhere
- ✅ JSON is valid (use an online JSON validator!)
- ✅ No typos in the file
- ✅ Docker runtime image uses
ghcr.io/ptero-eggs/yolks:*registry - ✅ Installer container uses
ghcr.io/ptero-eggs/installers:debianor:alpine - ✅ All file paths use forward slashes (/)
- ✅ No secrets or real tokens in default values
- ✅ Compared with similar working eggs for reference
- ✅ Ready to share! 🎉
📊 Quality Scoring
Rate your egg on these criteria:
| Criteria | Points | Your Score |
|---|---|---|
| Complete metadata | 10 | ___ |
| Well-documented variables (all fields present) | 15 | ___ |
| Working installation | 15 | ___ |
| Proper startup detection | 10 | ___ |
| Config file management | 15 | ___ |
| Error handling in install script | 10 | ___ |
| Correct registries and paths | 10 | ___ |
| Testing | 10 | ___ |
| Total | 95 | ___ |
Score: 80+ = Great egg, 70+ = Good egg, below 70 = Needs work
🔄 Egg Creation Workflow
- Plan — Decide what you're creating and what variables are needed
- Research — Look at similar existing eggs for reference
- Structure — Create the basic egg JSON structure
- Variables — Define all user-customizable settings with every required field
- Startup — Write and test the startup command
- Installation — Write the installation script (files go to
/mnt/server) - Configuration — Set up file management with parsers
- Documentation — Write a README explaining usage
- Testing — Thoroughly test everything from scratch
- Review — Check against this checklist
- Share — Release to the community!
🎓 Learning Resources
- Egg Basics — Understand fundamentals
- Configuration Variables — Variables in detail
- Extending Eggs — Real creation examples
- Wings Variables — Special panel variables
- File Parsers — Managing config files
- Wings Variables Extracted — Real eggs analyzed
💡 Pro Tips
- Start simple — Create a basic egg first, add features later
- Reference real eggs — Check Wings Variables Extracted for patterns
- Test often — Don't wait until the end to test
- Get feedback — Have others review your egg
- Keep improving — Version your eggs and improve over time
- Document well — Good descriptions help users understand what to fill in
- Be consistent — Follow naming patterns and conventions (SNAKE_CASE for env_variable)
- Validate JSON — Use an online JSON validator every time you edit the file
- Test edge cases — Not just the happy path
- Read error messages — They tell you exactly what's wrong!
🎉 You're Ready!
If you've completed this checklist, your egg is ready to share with the community!
Remember:
- Eggs are a single
.jsonfile — no folders, no separate scripts field_typeis always"text"— userulesto express the data type- Install path =
/mnt/server, runtime path =/home/container - Start simple, test thoroughly, help your users, keep improving
Good luck! 🥚🚀
Have questions? Check the complete documentation.
Ready to create? Start with Egg Basics.