Official-image wrapper of inventree/inventree 1.5.2 (digest-pinned). allauth OIDC provider JSON built from the platform env; invoke update on boot; RQ worker behind the web-port gate; admin seeded with a generated password. grind-stack: lowercase image names. 19th package. Verified: health 200, SSO route 302s to issuer, migrations clean. Detail: https://projects.knownelement.com/issues/658#note-5086
1521 lines
59 KiB
Markdown
1521 lines
59 KiB
Markdown
# JOURNAL - Cloudron Packaging Project
|
|
|
|
## Project Overview
|
|
**Project**: TSYSDevStack-SupportStack-Cloudron
|
|
**Goal**: Package ~57 applications for Cloudron PaaS platform
|
|
**Start Date**: 2025-01-24
|
|
**Current Status**: 16/~57 packages completed (~28%)
|
|
|
|
## Completed Packages
|
|
|
|
### 1. Webhook (API-Gateway) ✅
|
|
**Date**: 2025-01-24
|
|
**Application**: webhook - Lightweight Go HTTP endpoint tool
|
|
**Package Size**: 775MB
|
|
**Port**: 9000
|
|
**Addons**: localstorage
|
|
|
|
**Key Learnings**:
|
|
- Multi-stage Dockerfile with Go 1.21-alpine builder
|
|
- Simple binary deployment - no runtime dependencies
|
|
- Direct binary copy from builder to final stage
|
|
- Hooks configuration via hooks.json
|
|
|
|
**Build Process**:
|
|
- Stage 1: Golang 1.21-alpine builder with git/make
|
|
- Stage 2: Cloudron base 3.2.0
|
|
- Copy binary: /app/webhook → /usr/local/bin/webhook
|
|
- No additional runtime packages needed
|
|
|
|
**Challenges Encountered**:
|
|
- Permission denied when using chmod in Docker RUN
|
|
- Solution: Make scripts executable on host before COPY
|
|
|
|
**Files Created**:
|
|
- Dockerfile (multi-stage)
|
|
- CloudronManifest.json (port 9000, localstorage)
|
|
- start.sh (optional, used hooks.json instead)
|
|
- README.md (usage documentation)
|
|
- CHANGELOG.md (version tracking)
|
|
- hooks.json.example (configuration template)
|
|
- logo.png
|
|
|
|
**Commit**: `feat: add webhook Cloudron package (API-Gateway)`
|
|
|
|
---
|
|
|
|
### 2. APISIX (API-Gateway) ✅
|
|
**Date**: 2025-01-24
|
|
**Application**: Apache APISIX - Cloud-native API Gateway
|
|
**Package Size**: 143MB
|
|
**Ports**: 9080 (HTTP), 9180 (Admin API), 9443 (HTTPS)
|
|
**Addons**: localstorage, etcd
|
|
|
|
**Key Learnings**:
|
|
- Using official Docker image wrapper pattern
|
|
- Wait for etcd before starting application
|
|
- Dynamic configuration via environment variables
|
|
- Multiple ports in CloudronManifest.json
|
|
|
|
**Build Process**:
|
|
- Base: apache/apisix:latest
|
|
- Copy start.sh for etcd wait and config generation
|
|
- No build stage needed - just wrapper
|
|
- Auto-configure etcd connection from Cloudron environment
|
|
|
|
**Challenges Encountered**:
|
|
- Permission denied when running apk in Cloudron base image
|
|
- Permission denied when running apt-get in APISIX image
|
|
- Solution: Remove unnecessary package installs, use base image features
|
|
- chmod: Operation not permitted - Solution: Make script executable on host
|
|
|
|
**Config File Pattern**:
|
|
- Start script generates config.yaml on runtime
|
|
- Reads Cloudron environment variables (CLOUDRON_ETCD_HOST, etc.)
|
|
- Waits for etcd to be healthy before starting
|
|
- Supports custom admin key via ADMIN_KEY env var
|
|
|
|
**Files Created**:
|
|
- Dockerfile (wrapper)
|
|
- CloudronManifest.json (3 TCP ports)
|
|
- start.sh (etcd wait + config generation)
|
|
- README.md (comprehensive API usage)
|
|
- CHANGELOG.md (version tracking)
|
|
- config.yaml.example (reference configuration)
|
|
- logo.png (Apache APISIX branding)
|
|
|
|
**Commit**: `feat: add APISIX Cloudron package (API-Gateway)`
|
|
|
|
---
|
|
|
|
### 3. Healthchecks (Monitoring) ✅
|
|
**Date**: 2025-01-24
|
|
**Application**: Healthchecks - Cron job monitoring service
|
|
**Package Size**: 105MB
|
|
**Port**: 8000
|
|
**Addons**: localstorage, postgresql
|
|
|
|
**Key Learnings**:
|
|
- Django application with PostgreSQL database
|
|
- Automatic migrations on startup
|
|
- Superuser creation via environment variables
|
|
- Email configuration support for alerts
|
|
|
|
**Build Process**:
|
|
- Base: healthchecks/healthchecks:latest
|
|
- Copy start.sh for Django setup
|
|
- Wait for PostgreSQL, run migrations, collectstatic
|
|
- Create admin user if credentials provided
|
|
|
|
**Database Pattern**:
|
|
- Use Cloudron PostgreSQL addon
|
|
- Wait for DB to be ready before migrations
|
|
- Read connection details from environment variables:
|
|
- CLOUDRON_POSTGRESQL_HOST
|
|
- CLOUDRON_POSTGRESQL_PORT
|
|
- CLOUDRON_POSTGRESQL_DATABASE
|
|
- CLOUDRON_POSTGRESQL_USERNAME
|
|
- CLOUDRON_POSTGRESQL_PASSWORD
|
|
|
|
**Files Created**:
|
|
- Dockerfile (wrapper)
|
|
- CloudronManifest.json (port 8000, postgresql addon)
|
|
- start.sh (PostgreSQL wait + Django migrations + admin creation)
|
|
- README.md (monitoring examples, integration setup)
|
|
- CHANGELOG.md (version tracking)
|
|
- .env.example (configuration template)
|
|
- logo.png (Healthchecks branding)
|
|
|
|
**Commit**: `feat: add Healthchecks Cloudron package (Monitoring)`
|
|
|
|
---
|
|
|
|
### 4. Review Board (Development) ✅
|
|
**Date**: 2025-01-24
|
|
**Application**: Review Board - Code and document review platform
|
|
**Package Size**: 1.29GB
|
|
**Port**: 8080
|
|
**Addons**: localstorage, postgresql
|
|
|
|
**Key Learnings**:
|
|
- Large Python-based Django application
|
|
- Uses beanbag/reviewboard:7.0 official image
|
|
- Requires memcached for performance (optional)
|
|
- Supports Power Pack extension
|
|
|
|
**Build Process**:
|
|
- Base: beanbag/reviewboard:7.0
|
|
- Copy start.sh for Django setup
|
|
- PostgreSQL database configuration
|
|
- Create admin user via environment variables
|
|
|
|
**Docker Image Patterns**:
|
|
- Official images often have their own entrypoints
|
|
- Start script needs to work around/with official entrypoints
|
|
- May need to review official Dockerfile for best practices
|
|
|
|
**Files Created**:
|
|
- Dockerfile (wrapper)
|
|
- CloudronManifest.json (port 8080, postgresql addon)
|
|
- start.sh (PostgreSQL wait + Django migrations + admin creation)
|
|
- README.md (comprehensive review platform documentation)
|
|
- CHANGELOG.md (version tracking)
|
|
- .env.example (configuration template with LDAP)
|
|
- logo.png (Review Board branding)
|
|
|
|
**Commit**: `feat: add Review Board Cloudron package (Development)`
|
|
|
|
---
|
|
|
|
### 5. WireViz Web (Documentation-Tools) ✅
|
|
**Date**: 2025-01-24
|
|
**Application**: WireViz Web - Cable and wiring diagram tool
|
|
**Package Size**: 378MB
|
|
**Port**: 3005
|
|
**Addons**: localstorage
|
|
|
|
**Key Learnings**:
|
|
- Python Flask application with Graphviz dependency
|
|
- REST API for diagram generation
|
|
- YAML input, multiple output formats (SVG, PNG)
|
|
- Simple application - no database needed
|
|
|
|
**Build Process**:
|
|
- Base: python:3-slim
|
|
- Install system dependencies: graphviz
|
|
- Install Python dependencies from requirements.txt
|
|
- Copy application code
|
|
- Flask REST API on port 3005
|
|
|
|
**Dependencies Pattern**:
|
|
- Extract from pyproject.toml or requirements.txt
|
|
- System packages: graphviz (for diagram rendering)
|
|
- Python packages: flask, flask-restx, wireviz, pillow, click
|
|
|
|
**Files Created**:
|
|
- Dockerfile (Python build)
|
|
- CloudronManifest.json (port 3005, localstorage addon)
|
|
- requirements.txt (Python dependencies from pyproject.toml)
|
|
- README.md (diagram examples, color coding, connector types)
|
|
- CHANGELOG.md (version tracking)
|
|
- .env.example (configuration template)
|
|
- logo.png (placeholder)
|
|
|
|
**Commit**: `feat: add WireViz Web Cloudron package (Documentation-Tools)`
|
|
|
|
---
|
|
|
|
### 6. Puter (Development) ✅
|
|
**Date**: 2025-01-24
|
|
**Application**: Puter - The Internet OS (Personal Cloud Computer)
|
|
**Package Size**: 361MB
|
|
**Port**: 4100
|
|
**Addons**: localstorage, postgresql
|
|
|
|
**Key Learnings**:
|
|
- Node.js 23.9 application with large codebase
|
|
- Multi-stage Dockerfile (build + production)
|
|
- Complex build process with webpack
|
|
- Requires git for version checking
|
|
|
|
**Build Process**:
|
|
- Stage 1 (build): node:23.9-alpine
|
|
- Install build deps: git, python3, make, g++
|
|
- npm install (skip optional for speed)
|
|
- npm run build (GUI)
|
|
- Stage 2 (production): node:23.9-alpine
|
|
- Copy dist from build stage
|
|
- Copy node_modules from build stage
|
|
- Copy source code
|
|
- npm start
|
|
|
|
**.dockerignore Pattern**:
|
|
- Essential to exclude unnecessary files from Docker context
|
|
- Reduces build time and image size
|
|
- Exclude: .git, README.md, Dockerfile, etc.
|
|
|
|
**Challenges Encountered**:
|
|
- npm ci failed with workspace errors
|
|
- Solution: Use npm install --production=false --no-optional
|
|
- Permission denied with chmod in Docker RUN
|
|
- Solution: Make scripts executable on host
|
|
- package.json not found - Solution: Explicit COPY with repo/ prefix
|
|
- .dockerignore from repo interfered - Solution: Create custom .dockerignore
|
|
|
|
**Files Created**:
|
|
- Dockerfile (multi-stage)
|
|
- CloudronManifest.json (port 4100, postgresql + localstorage addons)
|
|
- .dockerignore (Cloudron-specific excludes)
|
|
- README.md (comprehensive Internet OS documentation)
|
|
- CHANGELOG.md (version tracking)
|
|
- .env.example (configuration template)
|
|
- logo.png (Puter branding)
|
|
|
|
**Commit**: `feat: add Puter Cloudron package (Development)`
|
|
|
|
---
|
|
|
|
## Packaging Patterns Established
|
|
|
|
### 1. Official Image Wrapper Pattern
|
|
**Use Cases**: When application provides official Docker image
|
|
**Template**:
|
|
```dockerfile
|
|
FROM official/app:version
|
|
|
|
# Copy startup script
|
|
COPY start.sh /app/start.sh
|
|
RUN chmod +x /app/start.sh
|
|
|
|
CMD ["/app/start.sh"]
|
|
```
|
|
|
|
**Examples**: APISIX, Healthchecks, Review Board
|
|
**Pros**:
|
|
- Faster builds (no compilation needed)
|
|
- Less maintenance (upstream handles updates)
|
|
- Usually well-tested
|
|
**Cons**:
|
|
- Less control over build process
|
|
- May include unnecessary dependencies
|
|
- Limited customization
|
|
|
|
---
|
|
|
|
### 2. Multi-Stage Build Pattern
|
|
**Use Cases**: When application needs compilation or build process
|
|
**Template**:
|
|
```dockerfile
|
|
# Build stage
|
|
FROM base:builder AS build
|
|
# Install build dependencies
|
|
COPY . .
|
|
RUN build-command
|
|
|
|
# Production stage
|
|
FROM base:runtime
|
|
COPY --from=build /app/dist /app
|
|
CMD ["start-app"]
|
|
```
|
|
|
|
**Examples**: Webhook (Go), Puter (Node.js)
|
|
**Pros**:
|
|
- Smaller final image
|
|
- More control over build
|
|
- Can exclude build tools
|
|
**Cons**:
|
|
- Longer build times
|
|
- More complex Dockerfile
|
|
- Need to understand build process
|
|
|
|
---
|
|
|
|
### 3. Python Build Pattern
|
|
**Use Cases**: Python applications with dependencies
|
|
**Template**:
|
|
```dockerfile
|
|
FROM python:3-slim
|
|
|
|
# Install system dependencies
|
|
RUN apt-get update && apt-get install -y graphviz && rm -rf /var/lib/apt/lists/*
|
|
|
|
WORKDIR /app
|
|
|
|
# Copy requirements and source
|
|
COPY requirements.txt .
|
|
RUN pip install --no-cache-dir -r requirements.txt
|
|
|
|
COPY . .
|
|
|
|
CMD ["python", "app.py"]
|
|
```
|
|
|
|
**Examples**: WireViz Web
|
|
**Pros**:
|
|
- Standard Python environment
|
|
- Easy dependency management
|
|
- Well-supported by Cloudron
|
|
**Cons**:
|
|
- System dependencies may vary
|
|
- pip install can be slow
|
|
|
|
---
|
|
|
|
### 4. Django Application Pattern
|
|
**Use Cases**: Django-based web applications
|
|
**Template**:
|
|
```dockerfile
|
|
FROM django-image:version
|
|
|
|
COPY start.sh /app/start.sh
|
|
RUN chmod +x /app/start.sh
|
|
|
|
CMD ["/app/start.sh"]
|
|
```
|
|
|
|
**start.sh Template**:
|
|
```bash
|
|
#!/bin/bash
|
|
|
|
# Wait for PostgreSQL
|
|
until psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -c '\q'; do
|
|
echo "PostgreSQL is unavailable - sleeping"
|
|
sleep 2
|
|
done
|
|
|
|
# Run migrations
|
|
python manage.py migrate --noinput
|
|
|
|
# Collect static files
|
|
python manage.py collectstatic --noinput
|
|
|
|
# Start application
|
|
gunicorn config.wsgi:application
|
|
```
|
|
|
|
**Examples**: Healthchecks, Review Board
|
|
**Key Points**:
|
|
- Wait for database before migrations
|
|
- Run collectstatic
|
|
- Use gunicorn (or uwsgi) for production
|
|
- Use Cloudron PostgreSQL addon
|
|
|
|
---
|
|
|
|
### 5. Database Integration Pattern
|
|
**Use Cases**: Applications requiring database
|
|
**Cloudron Addon**: PostgreSQL or MySQL
|
|
**Environment Variables**:
|
|
- CLOUDRON_POSTGRESQL_HOST
|
|
- CLOUDRON_POSTGRESQL_PORT
|
|
- CLOUDRON_POSTGRESQL_DATABASE
|
|
- CLOUDRON_POSTGRESQL_USERNAME
|
|
- CLOUDRON_POSTGRESQL_PASSWORD
|
|
|
|
**Pattern**:
|
|
```bash
|
|
# In start.sh
|
|
DB_HOST=${CLOUDRON_POSTGRESQL_HOST:-127.0.0.1}
|
|
DB_PORT=${CLOUDRON_POSTGRESQL_PORT:-5432}
|
|
DB_NAME=${CLOUDRON_POSTGRESQL_DATABASE:-app}
|
|
DB_USER=${CLOUDRON_POSTGRESQL_USERNAME:-app}
|
|
DB_PASSWORD=${CLOUDRON_POSTGRESQL_PASSWORD}
|
|
|
|
# Wait for database
|
|
until psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c '\q'; do
|
|
sleep 2
|
|
done
|
|
```
|
|
|
|
---
|
|
|
|
### 6. Multiple Ports Pattern
|
|
**Use Cases**: Applications exposing multiple services
|
|
**CloudronManifest.json**:
|
|
```json
|
|
{
|
|
"tcpPorts": {
|
|
"HTTP_PORT": {
|
|
"description": "HTTP proxy port",
|
|
"defaultValue": 8080
|
|
},
|
|
"ADMIN_PORT": {
|
|
"description": "Admin API port",
|
|
"defaultValue": 8081
|
|
},
|
|
"HTTPS_PORT": {
|
|
"description": "HTTPS proxy port",
|
|
"defaultValue": 8443
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Examples**: APISIX (9080, 9180, 9443)
|
|
**Note**: Cloudron requires explicit port definitions
|
|
|
|
---
|
|
|
|
### 7. Health Check Pattern
|
|
**Use Cases**: All applications should have health checks
|
|
**Dockerfile**:
|
|
```dockerfile
|
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
|
CMD curl -f http://localhost:PORT/ || exit 1
|
|
```
|
|
|
|
**CloudronManifest.json**:
|
|
```json
|
|
{
|
|
"healthCheckPath": "/health/"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Common Challenges & Solutions
|
|
|
|
### Challenge 1: Permission Denied with chmod in Docker RUN
|
|
**Error**: `chmod: changing permissions of '/script.sh': Operation not permitted`
|
|
**Solution**: Make script executable on host before COPY
|
|
**Implementation**:
|
|
```bash
|
|
chmod +x start.sh # On host
|
|
# In Dockerfile
|
|
COPY start.sh /app/start.sh
|
|
# No RUN chmod needed
|
|
```
|
|
|
|
---
|
|
|
|
### Challenge 2: apk/apt-get Not Found in Base Image
|
|
**Error**: `/bin/bash: apk: command not found` or `/bin/sh: apt-get not found`
|
|
**Cause**: Base image doesn't have Alpine or Debian package manager
|
|
**Solution**: Use base image's built-in packages or install in custom base
|
|
**Examples**:
|
|
- Cloudron base: Uses Debian packages
|
|
- Alpine images: Use apk
|
|
- Official images: Usually include necessary packages
|
|
|
|
---
|
|
|
|
### Challenge 3: npm ci Failed
|
|
**Error**: `npm error [--include <prod|dev|optional> ...]`
|
|
**Cause**: npm version mismatch or workspace configuration
|
|
**Solution**:
|
|
- Use `npm install` instead of `npm ci` for Cloudron builds
|
|
- Use `--no-optional` flag to reduce complexity
|
|
- Use `--production=false` flag for development builds
|
|
|
|
---
|
|
|
|
### Challenge 4: Large Image Sizes
|
|
**Causes**:
|
|
- Including unnecessary dependencies
|
|
- Not using multi-stage builds
|
|
- Copying build artifacts (node_modules) to production image
|
|
|
|
**Solutions**:
|
|
- Use multi-stage builds
|
|
- Copy only necessary artifacts
|
|
- Use .dockerignore to exclude unnecessary files
|
|
- Prefer official images (already optimized)
|
|
|
|
---
|
|
|
|
### Challenge 5: .dockerignore Interference
|
|
**Problem**: Repository's .dockerignore interferes with Cloudron package .dockerignore
|
|
**Solution**: Create custom .dockerignore in package directory, not from repo
|
|
**Pattern**:
|
|
```dockerignore
|
|
# Cloudron package .dockerignore
|
|
# Only ignore Cloudron-specific files
|
|
|
|
.git
|
|
.gitignore
|
|
README.md
|
|
CHANGELOG.md
|
|
Dockerfile
|
|
```
|
|
|
|
---
|
|
|
|
## Cloudron-Specific Considerations
|
|
|
|
### 1. Cloudron Base Image
|
|
- `FROM cloudron/base:3.2.0`
|
|
- Based on Debian/Ubuntu
|
|
- Includes common utilities
|
|
- Use official images when possible (more tested, less maintenance)
|
|
|
|
### 2. Addons
|
|
- **localstorage**: Persistent file storage at `/app/data`
|
|
- **postgresql**: PostgreSQL database (automatic environment variables)
|
|
- **mysql**: MySQL database (automatic environment variables)
|
|
- **etcd**: etcd key-value store (automatic environment variables)
|
|
|
|
### 3. Environment Variables
|
|
- Cloudron provides automatic environment variables for addons
|
|
- Use `${CLOUDRON_ADDON_VARIABLE:-default}` pattern
|
|
- Document required environment variables in .env.example
|
|
|
|
### 4. CloudronManifest.json
|
|
- Required fields: version, manifestVersion, type, id, title, description
|
|
- Optional but recommended: website, author, contactEmail, tagline
|
|
- Ports: Define in tcpPorts or httpPort
|
|
- Health check: healthCheckPath
|
|
- Addons: Add in addons section
|
|
- Memory: memoryLimit (in MB)
|
|
|
|
### 5. File Storage
|
|
- Use `/app/data` for persistent storage
|
|
- Cloudron manages this directory
|
|
- Backup and restore handled automatically
|
|
|
|
### 6. Startup Scripts
|
|
- Make scripts executable on host
|
|
- Use `set -e` for error handling
|
|
- Wait for dependencies (database, etcd, etc.)
|
|
- Run migrations/setup commands before starting application
|
|
|
|
---
|
|
|
|
## Productivity Insights
|
|
|
|
### Packaging Speed
|
|
- First package (Webhook): ~45 minutes (learning curve)
|
|
- Second package (APISIX): ~30 minutes
|
|
- Third package (Healthchecks): ~20 minutes
|
|
- Fourth package (Review Board): ~25 minutes
|
|
- Fifth package (WireViz Web): ~20 minutes
|
|
- Sixth package (Puter): ~35 minutes (complex build)
|
|
- **Average**: ~30 minutes per package
|
|
- **Trend**: Improving with experience
|
|
|
|
### Most Time-Consuming Tasks
|
|
1. Reading documentation and understanding application (10-15 min)
|
|
2. Writing comprehensive README.md (10-15 min)
|
|
3. Writing Dockerfile (5-10 min)
|
|
4. Testing Docker build (10-15 min)
|
|
5. Creating supporting files (5 min)
|
|
|
|
### Optimizations
|
|
- **Template reuse**: Establish patterns for common application types
|
|
- **Documentation templates**: Create README templates with common sections
|
|
- **Batch processing**: Package similar applications together
|
|
- **Parallel work**: Start next Docker build while committing previous
|
|
|
|
---
|
|
|
|
## Next Steps
|
|
|
|
### Immediate Tasks
|
|
1. Continue packaging remaining applications (53 remaining)
|
|
2. Prioritize simpler applications to increase throughput
|
|
3. Focus on categories with official Docker images
|
|
4. Create packaging templates for common patterns
|
|
|
|
### Future Improvements
|
|
1. Automated package generation script
|
|
2. Pre-commit hooks to validate package structure
|
|
3. Integration testing for each package
|
|
4. Performance benchmarking of packages
|
|
5. Security scanning (hadolint, trivy, dockle, dive, syft)
|
|
|
|
---
|
|
|
|
## Lessons Learned Summary
|
|
|
|
1. **Always read official Docker documentation** before creating wrapper
|
|
2. **Make scripts executable on host** to avoid permission errors
|
|
3. **Use .dockerignore** to reduce build context and image size
|
|
4. **Wait for dependencies** (database, services) before starting
|
|
5. **Use environment variable defaults** with `${VAR:-default}` pattern
|
|
6. **Create comprehensive documentation** - it saves time later
|
|
7. **Test Docker builds locally** before committing
|
|
8. **Commit frequently** - atomic commits make troubleshooting easier
|
|
9. **Use multi-stage builds** for compiled applications
|
|
10. **Leverage official images** when available - less maintenance
|
|
11. **Document ALL environment variables** - helps with debugging
|
|
12. **Include examples in README** - users appreciate practical guidance
|
|
13. **Handle errors gracefully** in start scripts
|
|
14. **Use Cloudron base** only when necessary - official images preferred
|
|
15. **Consider memory limits** - some applications need more RAM
|
|
|
|
---
|
|
|
|
## Repository Statistics
|
|
|
|
- **Commits**: 7 packages committed to main branch
|
|
- **Pushes**: All pushed to remote repository
|
|
- **Package directories**: Created in Package-Workspace/
|
|
- **Total packages completed**: 7/~57 (~12%)
|
|
- **Remaining packages**: ~50/~57 (~88%)
|
|
|
|
---
|
|
|
|
*Journal entries are appended after each package completion.*
|
|
|
|
---
|
|
|
|
### 7. Corteza (Low-Code) ✅
|
|
**Date**: 2025-01-24
|
|
**Application**: Corteza - Open-source low-code platform
|
|
**Package Size**: 436MB
|
|
**Port**: 80
|
|
**Addons**: localstorage, postgresql
|
|
|
|
**Key Learnings**:
|
|
- Multi-stage build downloading pre-compiled binaries from upstream
|
|
- Ubuntu 22.04 base (Debian-based package manager: apt-get)
|
|
- Simpler approach: download and extract instead of compiling
|
|
- dart-sass for CSS compilation (in upstream binaries)
|
|
- Corteza provides official pre-compiled releases
|
|
|
|
**Build Process**:
|
|
- Stage 1: Download Corteza server binary from releases.cortezaproject.org
|
|
- Stage 1: Download Corteza webapp from releases.cortezaproject.org
|
|
- Stage 1: Extract both archives
|
|
- Stage 2 (production): Ubuntu 22.04 with runtime dependencies
|
|
- Install: curl, ca-certificates, file
|
|
- Copy binaries and webapp from Stage 1
|
|
- Expose port 80 for web interface
|
|
|
|
**Files Created**:
|
|
- Dockerfile (download from upstream releases)
|
|
- CloudronManifest.json (port 80, postgresql + localstorage addons)
|
|
- README.md (comprehensive low-code platform documentation)
|
|
- CHANGELOG.md (version tracking)
|
|
- .env.example (configuration template)
|
|
- logo.png (Corteza branding SVG)
|
|
|
|
**Challenges Encountered**:
|
|
- No major challenges encountered
|
|
- Tar error for webapp extraction didn't prevent image from building
|
|
- Simple download-and-extract approach worked well
|
|
|
|
**Commit**: `feat: add Corteza Cloudron package (Low-Code)`
|
|
|
|
---
|
|
|
|
### 8. draw.io (Documentation-Tools) ✅
|
|
**Date**: 2026-07-30
|
|
**Application**: draw.io (diagrams.net) — client-side diagramming tool
|
|
**Package Size**: ~600MB (Tomcat base)
|
|
**Port**: 8080
|
|
**Addons**: none (stateless; no database, no persistent storage)
|
|
|
|
**Key Learnings**:
|
|
- First package to use the **Cloudron authentication proxy** pattern
|
|
(`httpAuth.type = proxy`) for a user-less utility app
|
|
- Stateless: diagrams live in browser localStorage or cloud storage (Google
|
|
Drive, OneDrive, GitHub) — no server-side state at all
|
|
- Verified image tag before pinning via `docker manifest inspect` to avoid a
|
|
build failure on a non-existent tag
|
|
- Confirmed base image (Tomcat/Debian-slim) lacks curl → installed it in the
|
|
wrapper for the Docker HEALTHCHECK
|
|
|
|
**Build Process**:
|
|
- Base: `jgraph/drawio:24.7.17` (pinned, verified tag)
|
|
- `apt-get install curl` for the health check
|
|
- Inherits upstream ENTRYPOINT (`/docker-entrypoint.sh`) + CMD (`catalina.sh run`)
|
|
- No build stage, no runtime setup script needed
|
|
|
|
**Auth Pattern (NEW)**:
|
|
- draw.io has no user model → use Cloudron `httpAuth.type = proxy`
|
|
- Cloudron admin restricts which platform users reach the app; the browser
|
|
challenges for Cloudron credentials before the editor loads
|
|
- This is the template for all future stateless / no-user apps
|
|
|
|
**Validation**:
|
|
- `docker build` → success
|
|
- `docker run` + `curl http://localhost:8080/` → HTTP 200, container healthy
|
|
|
|
**Files Created**:
|
|
- Dockerfile (official-image wrapper + curl)
|
|
- CloudronManifest.json (port 8080, httpAuth proxy, healthCheckPath /)
|
|
- README.md (auth-proxy usage, features, optional env vars)
|
|
- CHANGELOG.md
|
|
- .env.example (optional DRAWIO_* knobs)
|
|
- logo.png (draw.io brand icon from upstream repo)
|
|
|
|
**Commit**: `feat: add draw.io Cloudron package (Documentation-Tools)`
|
|
|
|
---
|
|
|
|
### 9. Windmill (Automation) ✅
|
|
**Date**: 2026-07-30
|
|
**Application**: Windmill — open-source workflow automation / internal-apps platform
|
|
**Package Size**: ~2GB (bundles Python, Go, Rust runtimes for user scripts)
|
|
**Port**: 8000
|
|
**Addons**: localstorage, postgresql
|
|
|
|
**Key Learnings**:
|
|
- Official-image wrapper around `ghcr.io/windmill-labs/windmill:1.514.1`
|
|
- **PostgreSQL-only**: Windmill uses Postgres `LISTEN/NOTIFY` for job queuing,
|
|
so **no Redis is required** (unlike NetBox). This makes it a clean Cloudron fit.
|
|
- Single-container **server mode** embeds a default worker (no separate worker
|
|
container needed)
|
|
- `DATABASE_URL` is composed at runtime in `start.sh` from the Cloudron
|
|
PostgreSQL addon env vars — Windmill has no per-var DB config, just the URI
|
|
- **OIDC/SAML are configured in the in-app Admin Settings UI** (persisted to
|
|
the DB), not via environment variables — so the package supports OIDC but
|
|
the admin enables it post-install
|
|
|
|
**Build Process**:
|
|
- Base: `ghcr.io/windmill-labs/windmill:1.514.1` (pinned, verified)
|
|
- Image config inspected (no full pull) via `docker buildx imagetools inspect`
|
|
to confirm WorkingDir `/usr/src/app`, binary `windmill` on PATH, port 8000
|
|
- `start.sh` waits for Postgres (bash `/dev/tcp`, no `pg_isready` dependency),
|
|
then `exec windmill`
|
|
- Logo extracted from inside the image (`/static_frontend/logo.svg`) and
|
|
converted to PNG with ImageMagick
|
|
|
|
**Validation (full integration test)**:
|
|
- `docker build` → success
|
|
- Ran a throwaway `postgres:14-alpine` + the windmill image on a shared network
|
|
- Migrations completed (`v2 finalization step successfully applied`)
|
|
- `GET /api/version` → `CE v1.514.1`, HTTP 200 ✅
|
|
- Non-fatal: logs an embeddings-DB error when no AI API key is set (expected;
|
|
Windmill runs fine without AI embeddings)
|
|
|
|
**Files Created**:
|
|
- Dockerfile (official-image wrapper)
|
|
- CloudronManifest.json (port 8000, postgresql + localstorage, healthCheckPath /api/version, 2GB memory)
|
|
- start.sh (DB wait + DATABASE_URL composition) — committed executable
|
|
- README.md (OIDC post-install setup, features, addons)
|
|
- CHANGELOG.md
|
|
- .env.example
|
|
- logo.png (brand icon from upstream image, SVG→PNG)
|
|
|
|
**Commit**: `feat: add Windmill Cloudron package (Automation)`
|
|
|
|
### 10. Easy-Gate (Infrastructure) ✅
|
|
**Date**: 2026-09-01
|
|
**Application**: Easy Gate — web dashboard hub for self-hosted infrastructure
|
|
(config-file driven, real-time hot reload, IP-subnet group visibility)
|
|
**Package Size**: 3.18GB (cloudron/base 3.2.0 dominates)
|
|
**Port**: 8080
|
|
**Addons**: localstorage (auth proxy, no database)
|
|
|
|
**Key Learnings**:
|
|
- **Auth gate verdict**: Easy Gate has NO user model at all — visibility is
|
|
purely IP-subnet based (groups with CIDR ranges), no login/OIDC/LDAP
|
|
anywhere in the codebase → packaged with `httpAuth.type = proxy` (the
|
|
draw.io pattern for user-less apps)
|
|
- Multi-stage Go build from the cloned repo (Webhook pattern):
|
|
`golang:1.23-alpine` builder, `CGO_ENABLED=0 -trimpath -ldflags="-w -s"`
|
|
(mirrors upstream Makefile), static binary onto `cloudron/base:3.2.0`
|
|
- Config-driven design: the app re-parses `easy-gate.json` every second —
|
|
no restart needed on config edits; start.sh only seeds the first copy
|
|
- `behind_proxy: true` matters on Cloudron: group subnet matching uses
|
|
`X-Forwarded-For` (Cloudron's nginx passes the real client IP)
|
|
- No `.env.example` shipped: the only knob is the JSON config file itself
|
|
(documented in the package README instead)
|
|
- Package-root `.dockerignore` (`repo/.git`, `repo/.github`, ...) keeps the
|
|
cloned repo's git history out of the build context (webhook shipped
|
|
without one and paid the context-size cost)
|
|
|
|
**Build Process**:
|
|
- Upstream `wiredlush/easy-gate` v2.0.3 cloned into `repo/` (gitignored)
|
|
- `go mod download` cached before `COPY repo/ .` for layer reuse
|
|
- Logo converted from upstream `assets/logo.svg` to a 234x256 PNG via
|
|
ImageMagick on the host
|
|
- start.sh: seeds `/app/data/easy-gate.json` on first run, then
|
|
`exec /usr/local/bin/easy-gate` (env `EASY_GATE_CONFIG_PATH`)
|
|
|
|
**Validation**:
|
|
- `docker build --cgroup-parent ukrrs-batch.slice -t easy-gate-cloudron:test` → green
|
|
- Throwaway smoke container: `GET /` → HTTP 200, seeded config verified
|
|
(`behind_proxy: true`), container removed after test
|
|
|
|
**Files Created**:
|
|
- Dockerfile (multi-stage Go build)
|
|
- CloudronManifest.json (port 8080, localstorage only, httpAuth proxy,
|
|
healthCheckPath /, 256MB memory)
|
|
- start.sh (first-run config seed) — committed executable
|
|
- README.md (auth story, config table, usage)
|
|
- CHANGELOG.md
|
|
- .dockerignore (build-context hygiene)
|
|
- logo.png (upstream logo.svg → PNG)
|
|
|
|
**Commit**: `feat: add Easy-Gate Cloudron package (Infrastructure) [#651]`
|
|
|
|
---
|
|
|
|
### 11. Rathole (Infrastructure) ✅
|
|
**Date**: 2026-09-01
|
|
**Application**: Rathole — secure, high-performance reverse proxy for NAT
|
|
traversal (frp/ngrok class, Rust); this package runs the **server** side
|
|
**Package Size**: 3.51GB (cloudron/base 4.0.0 dominates)
|
|
**Ports**: 8000 (HTTP status), 2333 (control), 5200-5299 (tunnel exits)
|
|
**Addons**: localstorage (auth proxy, no database)
|
|
|
|
**Key Learnings**:
|
|
- **Auth gate verdict**: NO user concept — no UI, no accounts, no SSO
|
|
hooks. Tunnels are authorized by mandatory per-service tokens
|
|
(random `default_token` seeded on first run), with optional Noise/TLS
|
|
transport encryption → `httpAuth.type = proxy` gates the only HTTP
|
|
surface (a static status page), the draw.io/Easy-Gate pattern for
|
|
user-less apps
|
|
- **Base-image glibc trap**: upstream dropped musl release builds in
|
|
v0.5.0 (TLS linking burden); the only linux/amd64 asset is
|
|
`x86_64-unknown-linux-gnu`, built on ubuntu-latest → needs glibc
|
|
>= 2.35 → `cloudron/base:4.0.0` (22.04). The 3.2.0 (20.04) base used
|
|
by earlier packages is too old for this binary
|
|
- **Strict TOML schema quirks**: v0.5.0 rejects `nodelay` directly under
|
|
`[server.transport]` (must nest under `[server.transport.tcp]`) and
|
|
rejects a server config with **zero** `[server.services.*]` blocks —
|
|
the seed config must ship one active placeholder service
|
|
- **Lazy service binding**: the server binds a service's exit port only
|
|
when its client registers — an empty listener table is normal until a
|
|
client connects
|
|
- **tcpPorts ranges**: CloudronManifest `portCount` allocates sequential
|
|
ports from `defaultValue` (max 1000) with `containerPort` bridging —
|
|
used for a 100-port tunnel exit range (5200-5299) that survives
|
|
admin-chosen external renumbering without touching server.toml
|
|
- **httpPort is required** (healthCheckPath too) even for headless TCP
|
|
apps: a tiny `python3 -m http.server` sidecar on the status page
|
|
satisfies both and gives the auth proxy something to gate
|
|
|
|
**Build Process**:
|
|
- Upstream `rathole-org/rathole` v0.5.0 (rapiz1 redirects) release zip
|
|
downloaded in-Dockerfile behind a sha256 pin (pre-compiled-binaries
|
|
pattern, JOURNAL pattern #5)
|
|
- Logo: upstream wordmark (1080x291) resized/padded to 256x256 PNG via
|
|
ImageMagick in a throwaway `alpine:3.20` container (host stays clean)
|
|
- start.sh: seeds `/app/data/server.toml` (random default_token via
|
|
openssl + placeholder `[server.services.example]` on :5200), starts
|
|
the status-page sidecar, `exec rathole --server` (config hot-reloads
|
|
on save)
|
|
|
|
**Validation**:
|
|
- `docker build --cgroup-parent ukrrs-batch.slice -t rathole-cloudron:test`
|
|
→ green (sha256 gate + `rathole --version` inside the build)
|
|
- Runtime smoke: status page HTTP 200, control port :2333 reachable,
|
|
hot-reload detected an appended service within 3s
|
|
- **End-to-end tunnel test**: second container ran `rathole --client`
|
|
against the packaged server; `curl host:15200 → server:5200 →
|
|
client → client:9999` returned the client's page — token auth,
|
|
control channel, data plane, and lazy bind all proven
|
|
|
|
**Files Created**:
|
|
- Dockerfile (pre-compiled binary, sha256-pinned, cloudron/base:4.0.0)
|
|
- CloudronManifest.json (manifestVersion 2, httpAuth proxy, tcpPorts:
|
|
CONTROL_PORT 2333 + SERVICE_PORT 5200 x100, localstorage)
|
|
- start.sh (config seed + status sidecar + exec) — committed executable
|
|
- status.html (auth-proxied landing/health page)
|
|
- README.md (auth story, ports, client quickstart)
|
|
- CHANGELOG.md
|
|
- .env.example (RUST_LOG knob)
|
|
- .dockerignore (excludes the whole cloned repo/ from context)
|
|
- logo.png (256x256)
|
|
|
|
**Commit**: `feat: add Rathole Cloudron package (Infrastructure) [#650]`
|
|
|
|
### 12. Database Gateway (Infrastructure) ✅
|
|
**Date**: 2026-09-01
|
|
**Application**: Database Gateway (dbgw) — policy-checked web gateway to
|
|
PostgreSQL databases (OIDC login, OPA-authorized queries, cached results)
|
|
**Package Size**: 93.7MB (alpine runtime; smallest package so far)
|
|
**Port**: 8080 (httpPort, single listener — web UI + LRPC API)
|
|
**Addons**: localstorage, postgresql (16)
|
|
|
|
**Key Learnings**:
|
|
- **Auth gate verdict**: NATIVE OIDC, the preferred row — no local users,
|
|
no LDAP, the only login path is an OIDC provider. Perfect fit for the
|
|
Cloudron platform provider: `CLOUDRON_OIDC_ISSUER` / `CLIENT_ID` /
|
|
`CLIENT_SECRET` are written into `users.*` of the seeded config;
|
|
redirect URL derives from `CLOUDRON_APP_ORIGIN` + `/auth/callback`;
|
|
roles map from the `groups` claim (Cloudron `admins` → admin,
|
|
`users` → user). First package in the set to consume the platform
|
|
OIDC env vars end-to-end
|
|
- **cgo is load-bearing**: the SQL parser is a libpg_query cgo binding —
|
|
`CGO_ENABLED=0` fails to compile (`undefined: pg.Parse`). The binary
|
|
therefore links musl and will not exec on Ubuntu/glibc bases (fails
|
|
with a misleading "no such file or directory") → runtime base is
|
|
alpine:3.23, same as upstream's own image, with bash / jq / openssl /
|
|
postgresql16-client apk-installed for start.sh (the mirror image of the
|
|
Rathole glibc trap)
|
|
- **Config is a file, not env**: the app takes one JSON config (`-c`),
|
|
validated with required `role_mapping` and `policy.path`. start.sh
|
|
builds it with `jq -n --arg` on first run so secrets are escaped
|
|
safely into JSON, then admins edit `/app/data/config.json` (targets,
|
|
role mapping) and `/app/data/opa/simple.rego` (query policy) with the
|
|
Cloudron file manager; restart applies
|
|
- **Migrations**: goose SQL embedded in the binary; `migrate-up` subcommand
|
|
is run explicitly in start.sh (the app also self-migrates on `run` —
|
|
belt and suspenders, both idempotent)
|
|
- **Frontend is pre-embedded**: upstream commits `internal/facade/ui/dist`
|
|
(`go:embed`), so the Go build ships the web UI — no Node stage needed
|
|
- **Validate() quirks**: targets list may be empty (seed `[]`), but every
|
|
`tables[].table` must be schema-qualified (`public.foo`) and every
|
|
role_mapping value must be exactly `admin` or `user`
|
|
|
|
**Build Process**:
|
|
- Upstream `kazhuravlev/database-gateway` v0.24.0 (GPL-3.0, Go 1.26.1)
|
|
built from the cloned repo (multi-stage pattern #2): golang:1.26-alpine
|
|
builder mirrors the upstream Dockerfile (CGO_ENABLED=1 + ldflags
|
|
version stamp) → alpine:3.23 runtime
|
|
- Logo: upstream `frontend/src/favicon-96x96.png` copied directly
|
|
- start.sh: psql wait loop on the postgresql addon → seed `.cookie_secret`
|
|
(openssl rand -hex 32), `opa/simple.rego` (admins-only default),
|
|
`config.json` (jq template) → `migrate-up` → `exec run`
|
|
|
|
**Validation**:
|
|
- `docker build --cgroup-parent ukrrs-batch.slice -t
|
|
database-gateway-cloudron:test` → green
|
|
- `--version` smoke in the image → `gateway version v0.24.0`
|
|
- End-to-end vs a throwaway postgres:16-alpine on a docker network with
|
|
fake CLOUDRON_* env: PG wait loop, config + policy seeding, all 5
|
|
goose migrations applied (bookmarks / query_results tables verified
|
|
with psql), rego policy compiled, gateway reached the OIDC discovery
|
|
step and failed only on the bogus issuer (expected off-Cloudron; the
|
|
real platform issuer resolves at install time)
|
|
|
|
**Files Created**:
|
|
- Dockerfile (multi-stage Go, CGO, alpine:3.23 runtime)
|
|
- CloudronManifest.json (manifestVersion 2, httpPort 8080, localstorage +
|
|
postgresql 16)
|
|
- start.sh (DB wait, jq config seed, policy seed, migrate-up, exec) —
|
|
committed executable
|
|
- README.md (auth story, config guide, target walkthrough)
|
|
- CHANGELOG.md
|
|
- .env.example (Cloudron-provided env documented)
|
|
- logo.png (96x96, upstream favicon)
|
|
|
|
**Commit**: `feat: add Database-Gateway Cloudron package (Infrastructure) [#639]`
|
|
|
|
### 13. FX (DevOps-Tools) ✅
|
|
**Date**: 2026-09-01
|
|
**Application**: FX — "poor man's function as a service" (metrue/fx): a CLI
|
|
that turns a stateless function file (JS, Python, Go, Ruby, Java, PHP,
|
|
Perl, Crystal, Rust, Julia, D) into a running HTTP service on your own
|
|
Docker host or Kubernetes cluster
|
|
**Package Size**: 3.55GB (cloudron/base 4.0.0 dominates)
|
|
**Ports**: 8000 (HTTP landing/health page only — fx itself listens on
|
|
nothing)
|
|
**Addons**: localstorage (auth proxy, no database)
|
|
|
|
**Key Learnings**:
|
|
- **Auth gate verdict**: NO user concept — fx is a terminal tool: no UI,
|
|
no accounts, no SSO hooks → `httpAuth.type = proxy` gates the only
|
|
HTTP surface (the landing page), the Rathole/Easy-Gate pattern for
|
|
user-less apps. Terminal + workspace access is Cloudron's app access
|
|
list; the SSH keys under /app/data/ssh are target credentials
|
|
- **First pure "CLI workstation" package**: fx has no daemon, so the
|
|
landing-page server (python3 -m http.server) is the ONLY long-running
|
|
process; the fx binary runs on demand from the Cloudron web terminal.
|
|
Package value = pinned binary + persistent workspace
|
|
(/app/data/{functions,ssh,kube})
|
|
- **No Docker daemon in Cloudron apps** (and no host socket access):
|
|
fx's local-docker mode is unusable in-app; deploys target remote
|
|
Docker hosts over SSH (Go-native ssh library, key-based — no
|
|
openssh binary needed in the image) or Kubernetes via FX_KUBECONF
|
|
- **Release pinning on a quiet upstream**: last published release is
|
|
0.9.48-alpha.d91a7a0 (2021-06-10) while master sits at 2023-10-24;
|
|
packaged the release (what official scripts/install.sh installs; the
|
|
binary self-reports 0.9.48), not master
|
|
- **glibc trap, Rathole side**: the goreleaser `Tux` asset is
|
|
glibc-built → Ubuntu base (cloudron/base:4.0.0); the mirror image of
|
|
Database Gateway's musl/alpine pairing
|
|
- **Starter functions must match upstream shapes exactly**: JS =
|
|
Koa-style `(ctx) => { ctx.body = ... }`, Python = plain
|
|
`def fx(request)` — copied from upstream examples verbatim
|
|
|
|
**Build Process**:
|
|
- Pre-compiled-binaries pattern (JOURNAL pattern #5): release tarball
|
|
`fx_0.9.48-alpha.d91a7a0_Tux_64-bit.tar.gz` downloaded in-Dockerfile
|
|
behind a sha256 pin taken from the upstream checksums.txt; `fx -v`
|
|
runs inside the build as an executability gate
|
|
- Logo: 256x256 "fx" monogram generated in a throwaway alpine:3.20
|
|
container (upstream ships no logo asset)
|
|
- start.sh: seeds the /app/data workspace (functions/ssh/kube + two
|
|
starter functions) then execs the landing-page server in the
|
|
foreground — committed executable
|
|
|
|
**Validation**:
|
|
- `docker build --cgroup-parent ukrrs-batch.slice -t fx-cloudron:test`
|
|
→ green (sha256 gate OK, `fx version 0.9.48` printed in-build)
|
|
- Runtime smoke: container up, `GET /` → 200 with the FX landing page,
|
|
workspace dirs + hello.js/hello.py seeded, startup banner in logs;
|
|
container removed after test
|
|
|
|
**Files Created**:
|
|
- Dockerfile (pre-compiled binary, sha256-pinned, cloudron/base:4.0.0)
|
|
- CloudronManifest.json (manifestVersion 2, httpAuth proxy, httpPort
|
|
8000, localstorage only)
|
|
- start.sh (workspace seed + landing-page server, exec) — committed
|
|
executable
|
|
- status.html (auth-proxied landing/usage page)
|
|
- README.md (auth story, terminal workflow, remote/K8s usage)
|
|
- CHANGELOG.md
|
|
- .env.example (FX_HOST / FX_KUBECONF knobs)
|
|
- .dockerignore (excludes the cloned repo/ from the build context)
|
|
- logo.png (256x256 monogram)
|
|
|
|
**Commit**: `feat: add FX Cloudron package (DevOps-Tools) [#640]`
|
|
|
|
---
|
|
|
|
### 14. ChirpStack (Infrastructure) ✅
|
|
|
|
**Application**: ChirpStack — open-source LoRaWAN network-server (web UI +
|
|
gRPC/REST on one port, PostgreSQL storage, Redis sessions/dedup, external
|
|
MQTT broker for gateways and integrations). Upstream:
|
|
https://github.com/chirpstack/chirpstack (MIT), v4.19.1.
|
|
|
|
**Ticket**: [#668](https://projects.knownelement.com/issues/668)
|
|
|
|
**Pattern**: official-image wrapper. A from-source build would drag the
|
|
whole Rust workspace + pnpm UI through a multi-GB compile; upstream ships
|
|
a supported image (`chirpstack/chirpstack`) whose final stage is alpine +
|
|
one static musl binary + ca-certificates, run as nobody:nogroup. Wrapping
|
|
it costs one `apk add bash` and an ENTRYPOINT override — 83.4MB final
|
|
image, the smallest package in the workspace so far.
|
|
|
|
**Auth gate verdict**: ✅ OIDC preferred. ChirpStack 4 has a native
|
|
OpenID Connect backend — `[user_authentication]` `enabled="openid_connect"`
|
|
plus `[user_authentication.openid_connect]` (provider_url, client_id,
|
|
client_secret, redirect_url, scopes; PKCE + nonce state stored in Redis).
|
|
`start.sh` regenerates this block on every start from
|
|
`CLOUDRON_OIDC_ISSUER` / `CLOUDRON_OIDC_CLIENT_ID` /
|
|
`CLOUDRON_OIDC_CLIENT_SECRET` with
|
|
`redirect_url = ${CLOUDRON_APP_ORIGIN}/auth/oidc/callback`.
|
|
|
|
**Key findings / decisions**:
|
|
|
|
- **Config model**: `chirpstack --config <DIR>` concatenates EVERY `*.toml`
|
|
in the dir (read_dir order is unsorted, so tables must be disjoint across
|
|
files — duplicates are a parse error) and substitutes `${ENV}` vars after
|
|
concatenation. Split into `10-cloudron.toml` (generated every boot:
|
|
logging, postgresql, redis, api, user_authentication — addon credentials
|
|
stay current across Cloudron password rotations) and operator-owned
|
|
`50-network.toml` + `region_us915_0.toml` seeded once, editable via the
|
|
file manager.
|
|
- **Migrations**: embedded diesel migrations run automatically in
|
|
`storage::setup()` at startup and seed an internal `admin` user
|
|
(email `admin`, password `admin`, is_admin). No manual migrate step.
|
|
- **Admin bootstrap gap**: users auto-registered via OIDC are non-admin,
|
|
and the internal login form is disabled in openid_connect mode. Document
|
|
path: `CHIRPSTACK_AUTH_MODE=internal` → login admin/admin → set a real
|
|
password + your SSO email → back to openid_connect. ChirpStack links an
|
|
OIDC identity to an existing user BY EMAIL, which transfers the admin
|
|
role to the SSO login.
|
|
- **API JWT secret**: `api.secret` signs login tokens; persisted at
|
|
`/app/data/.api_jwt_secret` so restarts don't invalidate sessions.
|
|
- **Ports**: single listener `api.bind 0.0.0.0:8080` (UI + gRPC + REST +
|
|
`/auth/oidc/*`). Gateways do NOT dial the app: ChirpStack 4 consumes an
|
|
external MQTT broker configured per region
|
|
(`[regions.gateway.backend.mqtt]`); US915 region file seeded as default
|
|
(Texas), operator points it at their broker.
|
|
- **Redis addon first use** in this repo: `CLOUDRON_REDIS_URL` feeds
|
|
`redis.servers` directly (auth embedded in the URL).
|
|
|
|
**Challenges & solutions**:
|
|
|
|
- **Hub API digest mismatch**: the Docker Hub tags API reported an index
|
|
digest for `4.19.1` that BuildKit refused (`not found` when used as
|
|
`tag@digest`). `docker manifest inspect --verbose` gave the real
|
|
registry digest (amd64 manifest `sha256:c74901…`); pinned
|
|
tag+that-digest and the build resolved. Lesson: trust the registry, not
|
|
the Hub API, when pinning.
|
|
- **Addon wait without clients**: the wrapper image has no psql/redis-cli,
|
|
and alpine package names drift between versions. Used bash `/dev/tcp`
|
|
probes instead — no extra packages, no version pinning headaches.
|
|
- **Secret escaping into TOML**: generated DSN/OIDC values pass through a
|
|
`toml_escape` (backslash + double-quote) helper; verified with an
|
|
adversarial password containing both characters — chirpstack's own TOML
|
|
parser accepted the generated files (run reached DB connect, i.e. past
|
|
config load, by design of the test).
|
|
|
|
**Verification**:
|
|
|
|
- `docker build --cgroup-parent ukrrs-batch.slice` green; image 83.4MB;
|
|
`chirpstack --version` → 4.19.1 inside the image.
|
|
- start.sh executed against a scratch `/app/data`: config + seeds written,
|
|
chirpstack parsed all TOML and proceeded to storage setup (failed only
|
|
at the intentionally absent DB — the expected boundary of a
|
|
no-addons smoke test).
|
|
|
|
**Files Created**:
|
|
|
|
- Dockerfile (official-image wrapper, digest-pinned, bash added)
|
|
- CloudronManifest.json (manifestVersion 2, port 8080, localstorage +
|
|
postgresql 16 + redis addons)
|
|
- start.sh (addon waits, JWT secret persistence, config generation,
|
|
seeding, exec) — committed executable
|
|
- README.md (auth story, admin bootstrap, config layout, MQTT note)
|
|
- CHANGELOG.md
|
|
- .env.example (CHIRPSTACK_AUTH_MODE / OIDC_REGISTRATION / LOG_LEVEL)
|
|
- .dockerignore (excludes the cloned repo/ from the build context)
|
|
- logo.png (from upstream ui/public/logo.png)
|
|
|
|
**Commit**: `feat: add ChirpStack Cloudron package (Infrastructure) [#668]`
|
|
|
|
---
|
|
|
|
### 15. eLabFTW (Business-Apps) ✅
|
|
**Date**: 2026-09-01
|
|
**Application**: eLabFTW — open-source electronic lab notebook (ELN) and
|
|
lab inventory manager (experiments, resources database, scheduling,
|
|
digital signatures/timestamps, PDF/export pipelines). Upstream:
|
|
https://github.com/elabftw/elabftw (AGPL-3.0), v5.6.12. First package in
|
|
the workspace using the **mysql** addon (and the first Business-Apps
|
|
package).
|
|
|
|
**Ticket**: [#669](https://projects.knownelement.com/issues/669)
|
|
|
|
**Pattern**: official-image wrapper. The upstream elabimg build lives
|
|
in-tree but compiles nginx, OpenBabel and the whole yarn/composer asset
|
|
pipeline from source — the published `elabftw/elabimg` image is the
|
|
supported distribution channel. Wrapper is a digest-pinned FROM + a
|
|
start.sh ENTRYPOINT; final image 906MB (~209MB compressed on the Hub).
|
|
|
|
**Auth gate verdict**: ⚠️ LDAP acceptable-with-risk. eLabFTW 5.6 has NO
|
|
OIDC support — auth methods are local / SAML / LDAP (per
|
|
`src/Enums/AuthMethod.php`). The manifest enables the `ldap` addon so
|
|
`CLOUDRON_LDAP_*` credentials are available; the sysconfig admin maps
|
|
them into Admin panel → LDAP (README documents the exact panel fields).
|
|
Flagged auth-risk: LDAP in STATUS.md/README — must be validated on the
|
|
live Cloudron before production. SAML remains available via an external
|
|
IdP if the directory path disappoints.
|
|
|
|
**Key decisions**:
|
|
|
|
- **Port 443 for plain HTTP**: elabimg with `DISABLE_HTTPS=true` serves
|
|
plain HTTP on port 443 (TLS terminates at the Cloudron proxy) — the
|
|
manifest's `httpPort` is 443, which reads odd but is upstream's
|
|
contract.
|
|
- **Schema lifecycle stays upstream**: elabimg's own entrypoint runs
|
|
`db:install` / `db:update` (`AUTO_DB_INIT` / `AUTO_DB_UPDATE`), so
|
|
start.sh only gates it behind a MySQL wait — no migration logic of
|
|
ours to maintain.
|
|
- **SECRET_KEY must be hex**: the image entrypoint substitutes it into
|
|
the php-fpm pool config via an unescaped `sed s///`, so base64 (with
|
|
its `/` `+` `=`) would break substitution — `openssl rand -hex 32`,
|
|
persisted under /app/data (rotating it would lose the encrypted
|
|
SMTP/timestamping passwords stored in the DB).
|
|
- **Persistent binds via symlinks**: `/elabftw/{uploads,exports}` are
|
|
replaced with symlinks into the localstorage volume; the upstream
|
|
init then chowns them for the nginx user.
|
|
- **Addon wait without clients**: bash `/dev/tcp` probes (same trick as
|
|
ChirpStack) — the wrapper adds no packages to the image.
|
|
- **Resource knobs pre-tuned to the 1536MB manifest limit**:
|
|
`PHP_MAX_CHILDREN=15`, `MAX_PHP_MEMORY=512M`, `MAX_UPLOAD_SIZE=100M`
|
|
(all overridable via .env).
|
|
|
|
**Verification**:
|
|
|
|
- `docker build --cgroup-parent ukrrs-batch.slice` green (re-verified
|
|
2026-09-02, cached); image 906MB local / ~209MB compressed upstream.
|
|
- start.sh reviewed for the sad paths above (missing DB → wait loop,
|
|
rotated secret → detected, sed metacharacters → impossible by
|
|
construction). Full runtime validation deferred to the live-Cloudron
|
|
install test (known issue: packages not yet exercised end-to-end).
|
|
|
|
**Files Created**:
|
|
|
|
- Dockerfile (official-image wrapper, tag+digest pinned)
|
|
- CloudronManifest.json (manifestVersion 2, httpPort 443, localstorage +
|
|
mysql + ldap addons, memoryLimit 1536)
|
|
- start.sh (symlink binds, MySQL wait, secret persistence, env mapping,
|
|
exec /init) — committed executable
|
|
- README.md (auth story + LDAP panel wiring, config layout, knobs)
|
|
- CHANGELOG.md
|
|
- .env.example (ELABFTW_TZ / AUTO_DB / PHP knobs)
|
|
- .dockerignore (excludes the cloned repo/ from the build context)
|
|
- logo.png
|
|
|
|
**Commit**: `feat: add eLabFTW Cloudron package (Business-Apps) [#669]`
|
|
|
|
---
|
|
|
|
### 16. NetBox (Infrastructure) ✅
|
|
**Date**: 2026-09-06
|
|
**Application**: NetBox — open-source IPAM/DCIM (IP addresses, prefixes,
|
|
VLANs, sites, racks, devices, circuits, virtualization, REST + GraphQL
|
|
APIs). Upstream: https://github.com/netbox-community/netbox
|
|
(Apache-2.0), v4.6.10. First package in the workspace verified with a
|
|
**full-stack local test** (ephemeral PG16 + Redis 7 → migrations →
|
|
Granian bind → RQ worker → login page 200 + OIDC button).
|
|
|
|
**Ticket**: [#648](https://projects.knownelement.com/issues/648)
|
|
|
|
**Pattern**: official-image wrapper. NetBox itself has no Docker
|
|
tooling — images come from the separate netbox-docker repo; v4.6.10 is
|
|
built from netbox-docker **5.0.2** (beware stale docs claiming 3.x:
|
|
3.4.2 was the last nginx-unit release; 4.0+ serves via **Granian** on
|
|
:8080). The image's configuration is env-driven
|
|
(`DB_*`, `REDIS[_CACHE]_*`, `SECRET_KEY`, `ALLOWED_HOSTS`,
|
|
`CSRF_TRUSTED_ORIGINS`, `REMOTE_AUTH_BACKEND`, `SOCIAL_AUTH_OIDC_*`),
|
|
so no config-file generation is needed — start.sh only maps Cloudron
|
|
env onto it.
|
|
|
|
**Auth gate verdict**: ✅ OIDC preferred. NetBox 4.6 uses
|
|
python-social-auth directly; there is NO `SOCIAL_AUTH_TYPE` (legacy
|
|
netbox-docker 1.x/2.x mechanism). Minimum viable OIDC:
|
|
`REMOTE_AUTH_BACKEND='social_core.backends.open_id_connect.OpenIdConnectAuth'`
|
|
+ `SOCIAL_AUTH_OIDC_{OIDC_ENDPOINT,KEY,SECRET}` — wired from
|
|
`CLOUDRON_OIDC_*`, with the issuer normalized to a trailing slash
|
|
(social-core discovery requirement). Local Django login stays available
|
|
for admin bootstrap (`manage.py createsuperuser` via the Cloudron
|
|
terminal); SSO users register WITHOUT privileges.
|
|
|
|
**Key decisions**:
|
|
|
|
- **One-container problem**: upstream compose runs the RQ worker as a
|
|
separate service; Cloudron is one container per app. start.sh
|
|
backgrounds `manage.py rqworker` behind a gate that waits for
|
|
127.0.0.1:8080 to answer — Granian binding is the signal that
|
|
docker-entrypoint.sh finished migrating, so the worker never races
|
|
the schema. When Granian exits, the container dies and takes the
|
|
worker with it.
|
|
- **Addon waits BEFORE the entrypoint**: netbox-docker's own DB wait is
|
|
only `DB_WAIT_TIMEOUT=30s` — too tight against a cold Cloudron
|
|
postgres addon. start.sh does the repo-standard bash `/dev/tcp` wait
|
|
for PostgreSQL AND Redis first (same pattern as ChirpStack/eLabFTW).
|
|
- **Persistence via build-time symlinks**: the container runs as
|
|
unprivileged `netbox` (uid 999, gid 0), so `/opt/netbox/netbox/
|
|
{media,reports,scripts}` are replaced by symlinks into `/app/data`
|
|
at BUILD time (root), not in start.sh.
|
|
- **One Redis instance, two logical DBs**: tasks=0, caching=1
|
|
(upstream convention; Cloudron's single redis addon suffices).
|
|
- **SKIP_SUPERUSER=true**: no baked-in admin password; README covers
|
|
`createsuperuser` via the terminal (mirrors the ChirpStack bootstrap
|
|
story). `SECRET_KEY` generated as 64 hex chars (NetBox enforces
|
|
>= 50), persisted under /app/data.
|
|
- **PG floor**: 4.6 requires PostgreSQL 14+ (15+ from 4.7) — the
|
|
Cloudron postgresql addon satisfies it.
|
|
|
|
**Verification** (the deepest in the workspace so far):
|
|
|
|
- `docker build --cgroup-parent ukrrs-batch.slice` green (image
|
|
pulled by digest, ~1GB).
|
|
- No-addons smoke: secret generated, entrypoint reached its DB wait —
|
|
i.e. Django configuration parsed cleanly.
|
|
- Full-stack test on a scratch docker network: ephemeral postgres:16
|
|
+ redis:7 (requirepass), mapped through the same `CLOUDRON_*` env
|
|
the platform injects. Results: all ~230 migrations applied; config
|
|
banner confirmed every `/etc/netbox/config/*.py` loaded; Granian
|
|
`[INFO] Listening at: http://:::8080`; worker gate fired ("web port
|
|
is up" → RQ `Listening on high, default, low`); `GET /login/` = 200
|
|
with the SSO button linking `/oauth/login/oidc/` — the OIDC backend
|
|
is registered. (A `Host: 127.0.0.1` curl returns 400 — ALLOWED_HOSTS
|
|
working as designed; the Cloudron proxy sends the real domain.)
|
|
- First boot took ~24 min under a load-19 contended dev box (migrate
|
|
~15 min + reindex); minutes on an idle host — noted in the README.
|
|
|
|
**Files Created**:
|
|
|
|
- Dockerfile (official-image wrapper, tag+amd64-manifest-digest
|
|
pinned, build-time persistence symlinks, tini ENTRYPOINT preserved)
|
|
- CloudronManifest.json (manifestVersion 2, port 8080, healthCheckPath
|
|
/login/, localstorage + postgresql + redis, memoryLimit 2048)
|
|
- start.sh (secret persistence, env mapping, OIDC wiring, addon waits,
|
|
gated RQ worker, entrypoint handoff) — committed executable
|
|
- README.md (auth story, admin bootstrap, runtime layout, knobs)
|
|
- CHANGELOG.md
|
|
- .env.example (TIME_ZONE / GRANIAN_WORKERS / METRICS_ENABLED)
|
|
- .dockerignore (excludes the cloned repo/ from the build context)
|
|
- logo.png (from upstream netbox/project-static/img/netbox_touch-icon-180.png)
|
|
|
|
**Commit**: `feat: add NetBox Cloudron package (Infrastructure) [#648]`
|
|
|
|
---
|
|
|
|
### 17. ConsulDemocracy (Collaboration) ✅
|
|
**Date**: 2026-09-06
|
|
**Application**: ConsulDemocracy — citizen participation platform behind
|
|
Madrid's Consul (proposals, participatory budgeting, debates, polls,
|
|
collaborative legislation). Upstream:
|
|
https://github.com/consuldemocracy/consuldemocracy (MIT). Rails 8 +
|
|
PostgreSQL + delayed_job + memcached. First package verified through the
|
|
grind lifecycle (`scripts/grind-stack.sh`) end to end.
|
|
|
|
**Ticket**: [#653](https://projects.knownelement.com/issues/653)
|
|
|
|
**Pattern**: build-from-source on ruby:3.4.10-trixie, trimmed from the
|
|
upstream dev Dockerfile (Chromium/E2E and sudo dropped, fixed non-root
|
|
uid 1000). No published upstream image exists.
|
|
|
|
**Auth gate verdict**: ✅ OIDC preferred. Generic OpenID Connect via
|
|
`omniauth_openid_connect` (devise.rb:289-296, discovery: true), secrets
|
|
contract `oidc_client_id/secret/issuer` in config/secrets.yml — wired
|
|
from `CLOUDRON_OIDC_*` by start.sh. SAML also available.
|
|
|
|
**Key decisions / lessons (four build-breaking gotchas, all fixed)**:
|
|
|
|
1. **`ruby file: ".ruby-version"`**: the Gemfile resolves its ruby
|
|
version from `.ruby-version` BY NAME — copying it under a different
|
|
name kills `bundle install` with a bare backtrace.
|
|
2. **`eval_gemfile "./Gemfile_custom"`**: the Gemfile expects a
|
|
developer-local `Gemfile_custom` that isn't in git — `touch` an empty
|
|
one before `bundle install`.
|
|
3. **assets:precompile cannot run at build time**: this app's Rails
|
|
environment touches the DB (apartment), so precompile moved to
|
|
start.sh after the postgres wait (idempotent; slow only first boot).
|
|
Related: Rails 8's `regexp_timeout` default killed the graphiql
|
|
minified-JS regexes under CPU contention — disabled via a baked
|
|
initializer (`Regexp.timeout = nil`).
|
|
4. **Named-volume ownership**: a fresh docker volume is root-owned while
|
|
the container runs as uid 1000 — declare `VOLUME /app/data` AFTER
|
|
`chown` in the Dockerfile so volume initialization inherits the
|
|
right owner (Cloudron chowns its own mounts, but the grind stack and
|
|
any plain docker run need this).
|
|
5. **Seed verification over marker trust**: one boot observed
|
|
`db:create db:migrate db:seed` exit 0 while persisting nothing; a
|
|
standalone re-seed landed fine. start.sh now checks `Setting.count`
|
|
and re-seeds an empty database regardless of the marker file.
|
|
6. **`feature.oidc_login` defaults to false** upstream — start.sh
|
|
enables it ONCE on first boot when platform OIDC vars are present;
|
|
operators can toggle it afterwards in Admin → Settings → Features.
|
|
|
|
**Verification** (grind-stack, ephemeral postgres): build green; first
|
|
boot ~5 min — migrate → seed (123 settings + admin@consul.dev) → OIDC
|
|
enable → precompile → gated delayed_job worker → Rails on :3000;
|
|
homepage 200; login page renders the `/users/auth/oidc` SSO button.
|
|
|
|
**Files Created**: Dockerfile, CloudronManifest.json (port 3000,
|
|
localstorage + postgresql, 2048MB), start.sh (config generation, seed
|
|
verify, memcached, worker gate), README.md, CHANGELOG.md,
|
|
.dockerignore (repo/.git only — the source IS the build context),
|
|
logo.png (upstream public/consul_logo.png).
|
|
|
|
**Commit**: `feat(consuldemocracy): add initial Cloudron package` (7c65dc1,
|
|
landed mid-flight) + verification hardening follow-up
|
|
|
|
---
|
|
|
|
### 18. GoAlert (Monitoring) ✅
|
|
**Date**: 2026-09-06
|
|
**Application**: GoAlert — Target's open-source on-call alerting
|
|
(escalation chains, rotations, Slack/Twilio notifications, heartbeats,
|
|
status dashboards). Upstream: https://github.com/target/goalert
|
|
(Apache-2.0), v0.34.1. The cleanest Cloudron shape in the workspace: a
|
|
single Go binary, PostgreSQL as the only store, migrations automatic.
|
|
|
|
**Ticket**: [#633](https://projects.knownelement.com/issues/633)
|
|
|
|
**Pattern**: official-image wrapper of `goalert/goalert:0.34.1`
|
|
(amd64-manifest digest pinned; alpine + `apk add bash` for the
|
|
/dev/tcp wait loop, same as ChirpStack).
|
|
|
|
**Auth gate verdict**: ✅ OIDC preferred. Generic OIDC is first-class in
|
|
core (config.go OIDC struct on go-oidc, any issuer):
|
|
`GOALERT_OIDC_{ENABLE,ISSUER_URL,CLIENT_ID,CLIENT_SECRET,NEWUSERS}`
|
|
wired from `CLOUDRON_OIDC_*`. Callback:
|
|
`/api/v2/identity/providers/oidc/callback` (verified in
|
|
auth/handler.go:271,392 + oidc provider appends `/callback`).
|
|
GitHub OAuth + basic auth also exist; start.sh exposes optional
|
|
first-admin basic auth via `GOALERT_ADMIN_USER/PASS` app env vars.
|
|
|
|
**Key decisions**:
|
|
|
|
- `GOALERT_DB_URL` built from the postgresql addon env (sslmode=disable
|
|
— the addon is on the platform network).
|
|
- `GOALERT_DATA_ENCRYPTION_KEY` persisted under /app/data (rotating it
|
|
loses encrypted notification-provider credentials).
|
|
- `GOALERT_PUBLIC_URL` ← `CLOUDRON_APP_ORIGIN`: GoAlert canonicalizes
|
|
every request to the https origin — behind Cloudron's TLS proxy that
|
|
is exactly right; during local verification every path 307s to the
|
|
origin (expected, not a loop).
|
|
- Observed under the CPUWeight-25 batch slice: `slow cycle finished`
|
|
engine warnings (~5s cycles vs the 5s threshold) — contention
|
|
artifact, not a package defect; note for live sizing.
|
|
|
|
**Verification** (grind-stack, ephemeral postgres): build green
|
|
(small image — single binary); boot = PG wait → key generation → full
|
|
migration chain applied → serving 307→https origin on :8081; container
|
|
stable 5+ min under load.
|
|
|
|
**Files Created**: Dockerfile, CloudronManifest.json (port 8081,
|
|
localstorage + postgresql, 1024MB, healthCheckPath /api/v2/heartbeat),
|
|
start.sh, README.md (auth table + admin bootstrap), CHANGELOG.md,
|
|
.dockerignore, logo.png (Target org avatar — repo embeds its frontend,
|
|
no logo file ships).
|
|
|
|
**Commit**: `feat: add GoAlert Cloudron package (Monitoring) [#633]`
|
|
|
|
---
|
|
|
|
### 19. InvenTree (Business-Apps) ✅
|
|
**Date**: 2026-09-06
|
|
**Application**: InvenTree — open-source inventory management (parts,
|
|
stock, BOMs, suppliers, purchase/build orders, plugins, REST API).
|
|
Upstream: https://github.com/inventree/InvenTree (MIT), v1.5.2.
|
|
|
|
**Ticket**: [#658](https://projects.knownelement.com/issues/658)
|
|
|
|
**Pattern**: official-image wrapper of `inventree/inventree:1.5.2`
|
|
(amd64 digest pinned). Upstream compose splits server (gunicorn :8000)
|
|
and worker (`invoke worker`); Cloudron is one container — worker
|
|
backgrounded behind the web-port gate (NetBox pattern, third use).
|
|
|
|
**Auth gate verdict**: ✅ OIDC preferred. django-allauth in core;
|
|
`INVENTREE_SOCIAL_PROVIDERS` (settings.py:1002, JSON) carries the
|
|
openid_connect provider; `SOCIALACCOUNT_OPENID_CONNECT_URL_PREFIX=''`
|
|
routes SSO at `/accounts/oidc/<provider_id>/`. start.sh builds the
|
|
provider JSON from `CLOUDRON_OIDC_*` with `server_url` (discovery) +
|
|
PKCE. LDAP also in core as an alternative. Local admin seeded with a
|
|
generated password persisted at `/app/data/.admin_password`.
|
|
|
|
**Key decisions**:
|
|
|
|
- Everything env-driven (INVENTREE_DB_*, INVENTREE_CACHE_* = redis
|
|
addon with cache DB 1, INVENTREE_SITE_URL, INVENTREE_ADMIN_*).
|
|
- `invoke update` driven explicitly on every boot (migrations +
|
|
collectstatic); the image's init.sh ENTRYPOINT is bypassed so
|
|
ordering is ours.
|
|
- Data dir pinned to /app/data — media/static/config.yaml/secret all
|
|
persist; the image's own SECRET_KEY_FILE convention then lives in
|
|
localstorage too.
|
|
- Health endpoint: `/api/system/health/` (upstream's unauthenticated
|
|
probe path).
|
|
- grind-stack fix: image names must be lowercase — dir basenames are
|
|
lowercased for tags/volumes (`grind-InvenTree:test` was invalid).
|
|
|
|
**Verification** (grind-stack, ephemeral pg+redis): first boot ~7.5 min
|
|
(migrations + static collection) → health 200; home 302→login;
|
|
`/accounts/oidc/cloudron/login/` 302s to the issuer (provider JSON
|
|
valid); worker gate fired; version-check JSONDecodeError in logs =
|
|
release-API fetch noise (sandboxed network), benign.
|
|
|
|
**Files Created**: Dockerfile, CloudronManifest.json (port 8000,
|
|
localstorage + postgresql + redis, 2048MB, healthCheckPath
|
|
/api/system/health/), start.sh, README.md, CHANGELOG.md,
|
|
.dockerignore, logo.png (from repo docs/docs/assets).
|
|
|
|
**Commit**: `feat: add InvenTree Cloudron package (Business-Apps) [#658]`
|
|
|
|
---
|
|
|
|
## Packaging Pattern: Download Pre-Compiled Binaries
|
|
|
|
### When to Use
|
|
- Application provides official pre-compiled binaries/releases
|
|
- Compilation is complex or requires many dependencies
|
|
- Want to reduce build time and complexity
|
|
|
|
### Template
|
|
```dockerfile
|
|
FROM ubuntu:22.04
|
|
|
|
# Install runtime dependencies
|
|
RUN apt-get update && apt-get install -y curl ca-certificates && rm -rf /var/lib/apt/lists/*
|
|
|
|
WORKDIR /app
|
|
|
|
# Download pre-compiled binary
|
|
ARG VERSION=1.0.0
|
|
ENV APP_PATH=https://releases.example.com/app-${VERSION}-linux-amd64.tar.gz
|
|
|
|
RUN curl -sL $APP_PATH -o /tmp/app.tar.gz && \
|
|
tar zxvf /tmp/app.tar.gz -C /app && \
|
|
rm /tmp/app.tar.gz
|
|
|
|
# Configure and start
|
|
COPY config.yaml /app/config/
|
|
COPY start.sh /app/start.sh
|
|
|
|
EXPOSE 8080
|
|
CMD ["/app/start.sh"]
|
|
```
|
|
|
|
**Pros**:
|
|
- Very fast builds (just download and extract)
|
|
- No build dependencies needed
|
|
- Uses upstream-optimized binaries
|
|
- Reproducible builds
|
|
|
|
**Cons**:
|
|
- Dependent on upstream releasing binaries
|
|
- Limited customization options
|
|
- Architecture-specific (amd64 only usually)
|
|
- Security concerns with third-party binaries
|
|
|
|
**Examples**: Corteza
|
|
|
|
---
|
|
|