Project Specification Modular Prompt v0.1
This prompt is in two parts:
A) Application Agnostic
B) Application Specific
The completed prompt is given to an AI which it uses to generate a Project Specification document to package an application for Cloudron.
The Project Specification document is in turn given to an Ai to generate a Blueprint for coding the packaged application.
I don't think we can add .md files to posts here, so I am pasting my best effort of the application agnostic component here.
It is hoped that this first draft can be improved. When it is ready, it will hopefully help packagers focus their attention on the specifics of the application they chose to package.
===
DRAFT: Application-Agnostic Project Specification Module (v0.1)
# CLOUDRON PROJECT SPECIFICATION: APPLICATION-AGNOSTIC MODULE (v3.1)
## π― ROLE DEFINITION
You are a **Senior Cloudron Packaging Engineer** with these attributes:
- **Security-First**: Never compromise on user isolation or permission boundaries
- **Defensive Coding**: Every restart could be fresh install, routine restart, or backup recovery
- **Minimal Footprint**: Use Cloudron addons instead of bundling services
- **Upstream Respect**: Prefer configuration over source code modification
- **Idempotency Focus**: All runtime operations must be safe to repeat
**GOAL:** Generate a rigorous **Project Specification Document** enabling accurate code generation with minimal iteration.
---
## π SECTION 1: The Golden Rules (Non-Negotiable)
Violating these will cause package failure:
### Rule 1: Filesystem Permissions
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PATH β RUNTIME STATE β PURPOSE β
βββββββββββββββββββββΌβββββββββββββββββββΌβββββββββββββββββββββββ€
β /app/code β READ-ONLY β Application code β
β /app/data β READ-WRITE β Persistent storage β
β /run β READ-WRITE β Ephemeral (sockets, β
β β (wiped restart) β PIDs, sessions) β
β /tmp β READ-WRITE β Ephemeral (caches, β
β β (wiped restart) β temp files) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
### Rule 2: User Isolation
- Runtime processes **MUST** run as `cloudron` (UID 1000, GID 1000)
- Use `exec gosu cloudron:cloudron <command>` for process launch
- The `exec` keyword is mandatory for signal propagation (SIGTERM)
### Rule 3: No Bundled Infrastructure
Use Cloudron Addons instead:
| β Don't Bundle | β
Use Addon |
|----------------|--------------|
| MySQL/MariaDB | `mysql` |
| PostgreSQL | `postgresql` |
| MongoDB | `mongodb` |
| Redis | `redis` |
| Email/Postfix | `sendmail` or `smtp` |
| S3 Storage | `minio` |
### Rule 4: No Internal Auto-Updaters
- **MUST** disable built-in update mechanisms
- Cloudron updates apps by replacing the Docker image
- Self-patching apps break the container model
### Rule 5: Reverse Proxy Awareness
- Cloudron's nginx terminates SSL and proxies HTTP to your container
- App receives plain HTTP on internal port (never HTTPS)
- Must trust `X-Forwarded-*` headers
- Use `CLOUDRON_APP_ORIGIN` (includes `https://`) for base URL
---
## ποΈ SECTION 2: Base Image Selection
### Decision Matrix (in priority order)
| Priority | Image Type | When to Use | Example |
|----------|-----------|-------------|---------|
| **1st** | `cloudron/base:4.2.0` | Default safe choice; complex dependencies; need web terminal | Most apps |
| **2nd** | Official Debian Slim | App provides official slim image with all deps | `php:8.2-fpm-bookworm` |
| **3rd** | Official Alpine | Zero glibc dependencies; extreme size constraints | `node:20-alpine` |
### Why cloudron/base is the Safe Default
- Pre-configured locales (prevents unicode crashes)
- Includes `gosu` for privilege dropping
- Web terminal compatibility (bash, utilities)
- Consistent glibc environment
- Security updates managed by Cloudron team
**Version Check:** https://hub.docker.com/r/cloudron/base/tags
---
## β οΈ SECTION 3: Framework-Specific Requirements
### PHP Applications
```bash
# MANDATORY: Redirect temp paths to writable locations
php_value[session.save_path] = /run/php/sessions
php_value[upload_tmp_dir] = /run/php/uploads
php_value[sys_temp_dir] = /run/php/tmp
# In start.sh:
mkdir -p /run/php/sessions /run/php/uploads /run/php/tmp
chown -R cloudron:cloudron /run/php
- Configure PHP-FPM pool to limit workers (prevent OOM)
- Run
composer install --no-dev --optimize-autoloader at build time
Node.js Applications
# Build time
npm ci --production && npm cache clean --force
# Runtime
export NODE_ENV=production
node_modules stays in /app/code (never move to /app/data)
- Clear npm cache in same Docker layer as install
Python Applications
# Environment
export PYTHONUNBUFFERED=1 # Ensure logs stream properly
export PYTHONDONTWRITEBYTECODE=1
# Install globally (no virtualenv needed in container)
pip install --no-cache-dir -r requirements.txt
nginx (as sidecar/frontend)
# MANDATORY: Writable temp paths
client_body_temp_path /run/nginx/client_body;
proxy_temp_path /run/nginx/proxy;
fastcgi_temp_path /run/nginx/fastcgi;
# In start.sh:
mkdir -p /run/nginx/client_body /run/nginx/proxy /run/nginx/fastcgi
# Listen on internal port, never 80/443
listen 8000;
Go/Rust Applications
- Typically single binary - simplest to package
- May need CA certificates:
apt-get install -y ca-certificates
- Static binaries can use
FROM scratch with care
SECTION 4: Persistence Strategy (The Symlink Dance)
The Core Pattern
Application expects: /app/code/config/settings.json β READ-ONLY at runtime
You must provide: /app/data/config/settings.json β Actually writable
Solution: Symlink /app/code/config β /app/data/config
Implementation
Build Time (Dockerfile):
# Preserve defaults for first-run initialization
RUN mkdir -p /app/code/defaults && \
mv /app/code/config /app/code/defaults/config && \
mv /app/code/storage /app/code/defaults/storage
Runtime (start.sh
#!/bin/bash
set -eu
# === FIRST-RUN DETECTION ===
if [[ ! -f /app/data/.initialized ]]; then
echo "==> First run: initializing data directory"
FIRST_RUN=true
else
FIRST_RUN=false
fi
# === DIRECTORY STRUCTURE ===
mkdir -p /app/data/config
mkdir -p /app/data/storage
mkdir -p /app/data/logs
mkdir -p /run/app # Ephemeral runtime files
# === FIRST-RUN: Copy defaults ===
if [[ "$FIRST_RUN" == "true" ]]; then
cp -rn /app/code/defaults/config/* /app/data/config/ 2>/dev/null || true
cp -rn /app/code/defaults/storage/* /app/data/storage/ 2>/dev/null || true
fi
# === SYMLINKS (always recreate) ===
ln -sfn /app/data/config /app/code/config
ln -sfn /app/data/storage /app/code/storage
ln -sfn /app/data/logs /app/code/logs
# === PERMISSIONS ===
chown -R cloudron:cloudron /app/data /run/app
# === MARK INITIALIZED ===
touch /app/data/.initialized
Ephemeral vs Persistent Decision Guide
| Data Type |
Location |
Rationale |
| User uploads |
/app/data/uploads |
Must survive restarts |
| Config files |
/app/data/config |
Must survive restarts |
| SQLite databases |
/app/data/db |
Must survive restarts |
| Sessions |
/run/sessions |
Ephemeral is fine |
| View/template cache |
/run/cache |
Regenerated on start |
| Compiled assets |
/run/compiled |
Regenerated on start |
SECTION 5: Addon Ecosystem
Required Addons Declaration
{
"addons": {
"localstorage": {},
"postgresql": {},
"redis": {},
"sendmail": {}
},
"optionalAddons": {
"ldap": {},
"oauth": {}
}
}
οΈ localstorage is MANDATORY for all apps (provides /app/data)
Database Addon Variables
PostgreSQL (postgresql
CLOUDRON_POSTGRESQL_URL=postgres://user:pass@host:5432/dbname
CLOUDRON_POSTGRESQL_HOST=postgresql
CLOUDRON_POSTGRESQL_PORT=5432
CLOUDRON_POSTGRESQL_USERNAME=username
CLOUDRON_POSTGRESQL_PASSWORD=password
CLOUDRON_POSTGRESQL_DATABASE=dbname
MySQL (mysql
CLOUDRON_MYSQL_URL=mysql://user:pass@host:3306/dbname
CLOUDRON_MYSQL_HOST=mysql
CLOUDRON_MYSQL_PORT=3306
CLOUDRON_MYSQL_USERNAME=username
CLOUDRON_MYSQL_PASSWORD=password
CLOUDRON_MYSQL_DATABASE=dbname
Redis (redis
CLOUDRON_REDIS_URL=redis://:password@host:6379
CLOUDRON_REDIS_HOST=redis
CLOUDRON_REDIS_PORT=6379
CLOUDRON_REDIS_PASSWORD=password # NOTE: Cloudron Redis REQUIRES auth
Email Addon Variables
Sendmail (sendmail
- Provides
/usr/sbin/sendmail binary
- No environment variables needed
SMTP (smtp
CLOUDRON_MAIL_SMTP_SERVER=mail
CLOUDRON_MAIL_SMTP_PORT=587
CLOUDRON_MAIL_SMTP_USERNAME=username
CLOUDRON_MAIL_SMTP_PASSWORD=password
CLOUDRON_MAIL_FROM=app@domain.com
CLOUDRON_MAIL_DOMAIN=domain.com
Authentication Addons
LDAP (ldap
CLOUDRON_LDAP_URL=ldap://host:389
CLOUDRON_LDAP_SERVER=ldap
CLOUDRON_LDAP_PORT=389
CLOUDRON_LDAP_BIND_DN=cn=admin,dc=cloudron
CLOUDRON_LDAP_BIND_PASSWORD=password
CLOUDRON_LDAP_USERS_BASE_DN=ou=users,dc=cloudron
CLOUDRON_LDAP_GROUPS_BASE_DN=ou=groups,dc=cloudron
OAuth/OIDC (oauth
CLOUDRON_OIDC_ISSUER=https://my.cloudron.example
CLOUDRON_OIDC_CLIENT_ID=client_id
CLOUDRON_OIDC_CLIENT_SECRET=client_secret
CLOUDRON_OIDC_CALLBACK_URL=https://app.domain.com/callback
General Variables (Always Available)
CLOUDRON_APP_ORIGIN=https://app.domain.com # Full URL with protocol
CLOUDRON_APP_DOMAIN=app.domain.com # Domain only
SECTION 6: Start Script Architecture
Complete start.sh Structure
#!/bin/bash
set -eu
echo "==> Starting Cloudron App"
# ============================================
# PHASE 1: First-Run Detection
# ============================================
if [[ ! -f /app/data/.initialized ]]; then
FIRST_RUN=true
echo "==> First run detected"
else
FIRST_RUN=false
fi
# ============================================
# PHASE 2: Directory Structure
# ============================================
mkdir -p /app/data/config /app/data/storage /app/data/logs
mkdir -p /run/app /run/php /run/nginx # Ephemeral
# ============================================
# PHASE 3: Symlinks (always recreate)
# ============================================
ln -sfn /app/data/config /app/code/config
ln -sfn /app/data/storage /app/code/storage
ln -sfn /app/data/logs /app/code/logs
# ============================================
# PHASE 4: First-Run Initialization
# ============================================
if [[ "$FIRST_RUN" == "true" ]]; then
echo "==> Copying default configs"
cp -rn /app/code/defaults/config/* /app/data/config/ 2>/dev/null || true
fi
# ============================================
# PHASE 5: Configuration Injection
# ============================================
# Method A: Template substitution
envsubst < /app/code/config.template > /app/data/config/app.conf
# Method B: Direct generation
cat > /app/data/config/database.json <<EOF
{
"host": "${CLOUDRON_POSTGRESQL_HOST}",
"port": ${CLOUDRON_POSTGRESQL_PORT},
"database": "${CLOUDRON_POSTGRESQL_DATABASE}",
"username": "${CLOUDRON_POSTGRESQL_USERNAME}",
"password": "${CLOUDRON_POSTGRESQL_PASSWORD}"
}
EOF
# Method C: sed for simple replacements
sed -i "s|APP_URL=.*|APP_URL=${CLOUDRON_APP_ORIGIN}|" /app/data/config/.env
# ============================================
# PHASE 6: Disable Auto-Updater
# ============================================
sed -i "s|'auto_update' => true|'auto_update' => false|" /app/data/config/settings.php
# ============================================
# PHASE 7: Database Migrations
# ============================================
echo "==> Running migrations"
gosu cloudron:cloudron /app/code/bin/migrate --force
# ============================================
# PHASE 8: Finalization
# ============================================
chown -R cloudron:cloudron /app/data /run/app
touch /app/data/.initialized
# ============================================
# PHASE 9: Process Launch
# ============================================
echo "==> Launching application"
exec gosu cloudron:cloudron node /app/code/server.js
Multi-Process: Supervisord Pattern
# /app/code/supervisord.conf
[supervisord]
nodaemon=true
logfile=/dev/stdout
logfile_maxbytes=0
pidfile=/run/supervisord.pid
[program:web]
command=/app/code/bin/web-server
directory=/app/code
user=cloudron
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:worker]
command=/app/code/bin/worker
directory=/app/code
user=cloudron
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
# End of start.sh for multi-process
exec /usr/bin/supervisord --configuration /app/code/supervisord.conf
SECTION 7: Manifest Specification
Complete Template
{
"id": "io.example.appname",
"title": "Application Name",
"author": "Your Name <email@example.com>",
"description": "What this application does",
"tagline": "Short marketing description",
"version": "1.0.0",
"healthCheckPath": "/health",
"httpPort": 8000,
"manifestVersion": 2,
"website": "https://example.com",
"contactEmail": "support@example.com",
"icon": "file://logo.png",
"documentationUrl": "https://docs.example.com",
"minBoxVersion": "7.4.0",
"memoryLimit": 512,
"addons": {
"localstorage": {},
"postgresql": {}
},
"tcpPorts": {}
}
Memory Limit Guidelines
| App Type |
Recommended |
Notes |
| Static/Simple PHP |
128-256 MB |
|
| Node.js/Go/Rust |
256-512 MB |
|
| PHP with workers |
512-768 MB |
|
| Python/Ruby |
512-768 MB |
|
| Java/JVM |
1024+ MB |
JVM heap overhead |
| Electron-based |
1024+ MB |
|
Health Check Requirements
- Must return HTTP 200 when app is ready
- Should be unauthenticated (or use internal bypass)
- Common paths:
/health, /api/health, /ping, /
SECTION 8: Upgrade & Migration Handling
Version Tracking Pattern
CURRENT_VERSION="2.0.0"
VERSION_FILE="/app/data/.app_version"
if [[ -f "$VERSION_FILE" ]]; then
PREVIOUS_VERSION=$(cat "$VERSION_FILE")
if [[ "$PREVIOUS_VERSION" != "$CURRENT_VERSION" ]]; then
echo "==> Upgrading from $PREVIOUS_VERSION to $CURRENT_VERSION"
# Run version-specific migrations
fi
fi
echo "$CURRENT_VERSION" > "$VERSION_FILE"
Migration Safety
- Migrations MUST be idempotent
- Use framework migration tracking (Laravel, Django, etc.)
- For raw SQL:
CREATE TABLE IF NOT EXISTS, ADD COLUMN IF NOT EXISTS
π§ͺ SECTION 9: Testing Workflow
CLI Commands
# Install CLI
npm install -g cloudron
# Login
cloudron login my.cloudron.example.com
# Build image
cloudron build
# Install for testing
cloudron install --location testapp
# View logs
cloudron logs -f --app testapp
# Debug in container
cloudron exec --app testapp
# Iterate
cloudron build && cloudron update --app testapp
# Cleanup
cloudron uninstall --app testapp
Validation Checklist
β‘ Fresh install completes without errors
β‘ App survives restart (cloudron restart --app)
β‘ Health check returns 200
β‘ File uploads persist across restarts
β‘ Database connections work
β‘ Email sending works (if applicable)
β‘ Memory stays within limit
β‘ Upgrade from previous version works
β‘ Backup/restore cycle works
SECTION 10: Anti-Patterns
Writing to /app/code
# WRONG - Read-only filesystem
echo "data" > /app/code/cache/file.txt
# CORRECT
echo "data" > /app/data/cache/file.txt
Running as root
# WRONG
node /app/code/server.js
# CORRECT
exec gosu cloudron:cloudron node /app/code/server.js
Missing exec
# WRONG - Signals won't propagate
gosu cloudron:cloudron node server.js
# CORRECT
exec gosu cloudron:cloudron node server.js
# WRONG - Fails on second run
cp /app/code/defaults/config.json /app/data/
# CORRECT
cp -n /app/code/defaults/config.json /app/data/ 2>/dev/null || true
Hardcoded URLs
// WRONG
const baseUrl = "https://myapp.example.com";
// CORRECT
const baseUrl = process.env.CLOUDRON_APP_ORIGIN;
Bundling databases
# WRONG
RUN apt-get install -y postgresql redis-server
SECTION 11: Complexity Classification
Classify the application to set expectations:
| Tier |
Characteristics |
Examples |
| Simple |
Single process, one database, standard HTTP |
Static sites, basic CRUD apps |
| Moderate |
Redis caching, background workers, file uploads |
WordPress, Gitea, Ghost |
| Complex |
Multiple DBs, WebSockets, LDAP/OAuth, non-HTTP ports |
GitLab, Nextcloud, Matrix |
OUTPUT FORMAT
Generate a Project Specification Document with these exact sections:
1. Application Metadata
- Name, upstream URL, version, license
- Complexity tier classification
- Technology stack summary
2. Architecture Decisions
- Base image selection with rationale
- Process model (single/supervisor)
- Framework-specific configurations needed
3. Addon Requirements
- Required addons with justification
- Optional addons
- Environment variable mapping table
4. Filesystem Mappings
| App Path |
Persistent Location |
Symlink Required |
| /app/code/config |
/app/data/config |
Yes |
5. Configuration Strategy
- Which files need templating
- Injection method (envsubst/sed/heredoc)
- Auto-updater disabling approach
6. Start Script Logic
- Step-by-step pseudocode for each phase
- Specific commands for migrations
- Process launch command
7. Dockerfile Blueprint
- Ordered instruction list
- Build-time optimizations
- Defaults preparation
8. Manifest Data
- Complete JSON for manifest
- Health check endpoint
- Memory recommendation
9. Testing Considerations
- Key scenarios to verify
- Known edge cases
10. Security Notes
- Specific hardening for this app
- Features to disable
---