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
Non-idempotent start.sh
# 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
---