<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[How to Package and Deploy Strapi v5 as a Custom App on Cloudron]]></title><description><![CDATA[<h1>How to Package and Deploy Strapi v5 as a Custom App on Cloudron</h1>
<p dir="auto">You people are amazing! I actually managed to deploy Strapi on Cloudron. I hope that we can support Strapi as an official application soon.</p>
<hr />
<blockquote>
<p dir="auto"><strong>Who is this guide for?</strong><br />
This guide covers two paths:</p>
<ul>
<li><strong>Path A — Personal/team installation:</strong> Deploy Strapi for your own Cloudron instance. No store submission required.</li>
<li><strong>Path B — Official packaging:</strong> Everything you need to eventually submit Strapi to the Cloudron App Store.</li>
</ul>
<p dir="auto">Both paths share the same core steps. Path B additions are clearly marked.</p>
</blockquote>
<hr />
<h2>What is Strapi?</h2>
<p dir="auto"><a href="https://strapi.io" target="_blank" rel="noopener noreferrer nofollow ugc">Strapi</a> is the leading open-source headless CMS. It gives you a self-hosted REST and GraphQL API with a beautiful admin panel for managing content. Strapi v5 (current stable) runs on Node.js 20/22 LTS and PostgreSQL.</p>
<hr />
<h2>Prerequisites</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Requirement</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr>
<td>Cloudron instance</td>
<td>v8.0.0 or later (base image 5.0.0)</td>
</tr>
<tr>
<td>Local machine</td>
<td>To run the Cloudron CLI and build the Docker image</td>
</tr>
<tr>
<td>Node.js 20 or 22 LTS</td>
<td>For the Cloudron CLI — install via <a href="https://github.com/nvm-sh/nvm" target="_blank" rel="noopener noreferrer nofollow ugc">nvm</a></td>
</tr>
<tr>
<td>pnpm</td>
<td>Preferred package manager — <code>curl -fsSL https://get.pnpm.io/install.sh \| sh -</code></td>
</tr>
<tr>
<td>Docker Hub account</td>
<td>Free at <a href="https://hub.docker.com" target="_blank" rel="noopener noreferrer nofollow ugc">hub.docker.com</a> — needed to host your built image</td>
</tr>
<tr>
<td>Podman or Docker</td>
<td>For building the image locally</td>
</tr>
</tbody>
</table>
<blockquote>
<p dir="auto"><strong>Memory note for the Cloudron team:</strong> Strapi v5 requires at least <strong>1 GB RAM</strong> to start reliably. The default 256 MB limit for custom apps will OOM-kill it. We'd love to see the default raised or a prominent warning in the CLI/docs for Node.js apps. In the meantime, set <code>"memoryLimit": 1073741824</code> in your manifest.</p>
</blockquote>
<hr />
<h2><img src="https://forum.cloudron.io/assets/plugins/nodebb-plugin-emoji/emoji/android/1f427.png?v=090c346130c" class="not-responsive emoji emoji-android emoji--penguin" style="height:23px;width:auto;vertical-align:middle" title="🐧" alt="🐧" /> Bazzite / Fedora Atomic Desktop users</h2>
<p dir="auto">Bazzite (and other Fedora Atomic/immutable desktops) ships <strong>Podman</strong> instead of Docker. Good news: Podman is a drop-in replacement for everything in this guide.</p>
<pre><code class="language-bash"># Install nvm (works on immutable systems — installs to ~/.nvm)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install 22 &amp;&amp; nvm alias default 22

# Install pnpm
curl -fsSL https://get.pnpm.io/install.sh | sh -
source ~/.bashrc

# Install Cloudron CLI
npm install -g cloudron

# Podman is already installed — log in to Docker Hub
podman login docker.io
</code></pre>
<blockquote>
<p dir="auto"><strong>Gotcha #1 — Podman OCI format warning:</strong> You'll see <code>SHELL is not supported for OCI image format</code> warnings during builds. These are harmless — add <code># syntax=docker/dockerfile:1</code> as the first line of your Dockerfile to suppress them, or use <code>podman build --format docker</code> flag.</p>
</blockquote>
<hr />
<h2>Step 1 — Create the package directory</h2>
<pre><code class="language-bash">mkdir -p ~/projects/strapi-cloudron
cd ~/projects/strapi-cloudron

mkdir -p strapi-app/config/env/production
mkdir -p strapi-app/src/api
mkdir -p strapi-app/public/uploads
</code></pre>
<p dir="auto">Your final structure will look like:</p>
<pre><code>strapi-cloudron/
├── Dockerfile
├── CloudronManifest.json
├── start.sh
├── .dockerignore
└── strapi-app/
    ├── package.json
    ├── config/
    │   ├── database.js
    │   ├── server.js
    │   └── admin.js
    ├── src/api/
    └── public/uploads/
</code></pre>
<hr />
<h2>Step 2 — Log in to Cloudron CLI</h2>
<pre><code class="language-bash">cloudron login your.cloudron.domain
# Enter your admin username, password, and 2FA token when prompted

cloudron list  # verify connection
</code></pre>
<blockquote>
<p dir="auto"><strong>Gotcha #2 — CLI runs on your local machine, not the server.</strong> Run all <code>cloudron</code> commands from your laptop/desktop, not via SSH on the server.</p>
</blockquote>
<hr />
<h2>Step 3 — Write the Strapi config files</h2>
<p dir="auto"><strong><code>strapi-app/package.json</code></strong></p>
<pre><code class="language-json">{
  "name": "strapi-app",
  "private": true,
  "version": "0.1.0",
  "scripts": {
    "develop": "strapi develop",
    "start": "strapi start",
    "build": "strapi build",
    "strapi": "strapi"
  },
  "dependencies": {
    "@strapi/strapi": "5",
    "@strapi/plugin-users-permissions": "5",
    "pg": "^8.11.0",
    "better-sqlite3": "^9.4.3",
    "react": "^18.0.0",
    "react-dom": "^18.0.0",
    "react-router-dom": "^6.0.0",
    "styled-components": "^6.0.0"
  },
  "engines": {
    "node": "&gt;=20.0.0 &lt;=22.x.x",
    "pnpm": "&gt;=8"
  }
}
</code></pre>
<blockquote>
<p dir="auto"><strong>Gotcha #3 — Strapi v5 package names changed.</strong> <code>@strapi/plugin-i18n</code> no longer exists as a separate package in v5 — it's built into core. Using <code>"^5.0.0"</code> version ranges also causes resolution failures; use <code>"5"</code> instead to get the latest stable v5.</p>
<p dir="auto"><strong>Gotcha #4 — Missing admin peer dependencies.</strong> Strapi v5's build step requires <code>react</code>, <code>react-dom</code>, <code>react-router-dom</code>, and <code>styled-components</code> listed explicitly in your <code>package.json</code>, or it will try to install them at build time inside the read-only container and fail.</p>
</blockquote>
<p dir="auto"><strong><code>strapi-app/config/database.js</code></strong></p>
<pre><code class="language-js">module.exports = ({ env }) =&gt; ({
  connection: {
    client: env('DATABASE_CLIENT', 'sqlite'),
    connection: {
      host: env('DATABASE_HOST', '127.0.0.1'),
      port: env.int('DATABASE_PORT', 5432),
      database: env('DATABASE_NAME', 'strapi'),
      user: env('DATABASE_USERNAME', 'strapi'),
      password: env('DATABASE_PASSWORD', ''),
      ssl: env.bool('DATABASE_SSL', false),
      filename: env('DATABASE_FILENAME', '.tmp/data.db'),
    },
    pool: { min: 2, max: 10 },
    acquireConnectionTimeout: 60000,
    useNullAsDefault: true,
  },
});
</code></pre>
<p dir="auto"><strong><code>strapi-app/config/server.js</code></strong></p>
<pre><code class="language-js">module.exports = ({ env }) =&gt; ({
  host: env('HOST', '0.0.0.0'),
  port: env.int('PORT', 8000),
  url: env('CLOUDRON_APP_ORIGIN', ''),
  app: {
    keys: env.array('APP_KEYS'),
  },
});
</code></pre>
<blockquote>
<p dir="auto"><strong>Gotcha #5 — Always set <code>url</code> to <code>CLOUDRON_APP_ORIGIN</code>.</strong> Cloudron injects this as the full public HTTPS URL (e.g. <code>https://cms.example.com</code>). Without it, media library URLs and admin panel asset paths will be wrong.</p>
</blockquote>
<p dir="auto"><strong><code>strapi-app/config/admin.js</code></strong></p>
<pre><code class="language-js">module.exports = ({ env }) =&gt; ({
  auth: {
    secret: env('ADMIN_JWT_SECRET'),
  },
  apiToken: {
    salt: env('API_TOKEN_SALT'),
  },
  transfer: {
    token: {
      salt: env('TRANSFER_TOKEN_SALT'),
    },
  },
  secrets: {
    encryptionKey: env('ADMIN_ENCRYPTION_KEY'),
  },
  flags: {
    nps: env.bool('FLAG_NPS', false),
    promoteEE: env.bool('FLAG_PROMOTE_EE', false),
  },
});
</code></pre>
<hr />
<h2>Step 4 — Write the Cloudron packaging files</h2>
<p dir="auto"><strong><code>CloudronManifest.json</code></strong></p>
<pre><code class="language-json">{
  "id": "com.yourorg.strapi",
  "title": "Strapi CMS",
  "author": "Your Name",
  "description": "Strapi v5 headless CMS",
  "tagline": "Open-source headless CMS",
  "version": "5.0.0",
  "healthCheckPath": "/_health",
  "httpPort": 8000,
  "addons": {
    "postgresql": {},
    "localstorage": {}
  },
  "manifestVersion": 2,
  "minBoxVersion": "8.0.0",
  "memoryLimit": 1073741824,
  "website": "https://strapi.io",
  "documentationUrl": "https://docs.strapi.io"
}
</code></pre>
<blockquote>
<p dir="auto"><strong>Gotcha #6 — <code>/_health</code> returns HTTP 204, not 200.</strong> Cloudron accepts any <code>2xx</code> so this is fine. Don't use <code>/</code> as your health check path — Strapi's root redirects to the admin panel and can be slow on cold start, causing false health check failures.</p>
<p dir="auto"><strong>Gotcha #7 — Memory limit is critical.</strong> The default Cloudron custom app limit is 256 MB. Strapi needs at least 1 GB. Set <code>"memoryLimit": 1073741824</code> (1 GB in bytes) or Strapi will be OOM-killed silently.</p>
</blockquote>
<p dir="auto"><strong><code>Dockerfile</code></strong></p>
<pre><code class="language-dockerfile"># syntax=docker/dockerfile:1
FROM docker.io/cloudron/base:5.0.0

RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
    &amp;&amp; apt-get install -y nodejs \
    &amp;&amp; rm -rf /var/cache/apt /var/lib/apt/lists

RUN npm install -g pnpm

RUN mkdir -p /app/code /app/data/uploads /app/data/database

WORKDIR /app/code
COPY strapi-app/ /app/code/

RUN pnpm install --frozen-lockfile=false
RUN pnpm approve-builds --yes || true
RUN pnpm rebuild better-sqlite3 sharp @swc/core esbuild
RUN NODE_ENV=production pnpm run build

RUN rm -rf /app/code/.tmp /app/code/public/uploads /app/code/database \
    &amp;&amp; ln -sf /run/strapi-tmp /app/code/.tmp \
    &amp;&amp; ln -sf /app/data/uploads /app/code/public/uploads \
    &amp;&amp; ln -sf /app/data/database /app/code/database

COPY start.sh /app/code/start.sh
RUN chmod +x /app/code/start.sh

CMD ["/app/code/start.sh"]
</code></pre>
<blockquote>
<p dir="auto"><strong>Gotcha #8 — Cloudron's filesystem is read-only at runtime.</strong> Only <code>/run</code> (ephemeral), <code>/app/data</code> (persistent, backed up), and <code>/tmp</code> are writable. Strapi needs to write to three locations — all must be symlinked in the Dockerfile:</p>
<ul>
<li><code>.tmp/</code> → <code>/run/strapi-tmp</code> (scratch space, ephemeral is fine)</li>
<li><code>public/uploads/</code> → <code>/app/data/uploads</code> (media files, must persist)</li>
<li><code>database/</code> → <code>/app/data/database</code> (migration tracking, must persist)</li>
</ul>
<p dir="auto"><strong>Gotcha #9 — Build the admin panel in the Dockerfile, not at startup.</strong> <code>pnpm run build</code> compiles Strapi's React admin panel (~10 seconds). If you defer this to <code>start.sh</code>, the container takes 3–5 minutes to start and health checks time out.</p>
<p dir="auto"><strong>Gotcha #10 — Native modules need explicit approval with pnpm.</strong> pnpm's security model blocks build scripts by default. Run <code>pnpm approve-builds</code> interactively during development, or pass <code>--yes</code> in the Dockerfile. The modules that need this are <code>better-sqlite3</code>, <code>sharp</code>, <code>@swc/core</code>, and <code>esbuild</code>.</p>
<p dir="auto"><strong>Gotcha #11 — Node.js version in the base image.</strong> <code>cloudron/base:5.0.0</code> (Ubuntu 24.04) ships a system Node.js that may not be v20/22 LTS. Install explicitly via NodeSource to guarantee Strapi v5 compatibility.</p>
</blockquote>
<p dir="auto"><strong><code>start.sh</code></strong></p>
<pre><code class="language-bash">#!/bin/bash
set -eu

echo "==&gt; Creating runtime directories..."
mkdir -p /run/strapi-tmp
mkdir -p /app/data/uploads
mkdir -p /app/data/database/migrations

echo "==&gt; Checking secrets..."
if [[ ! -f /app/data/.secrets ]]; then
    echo "==&gt; Generating secrets for first run..."
    cat &gt; /app/data/.secrets &lt;&lt; SECRETS
export APP_KEYS="$(openssl rand -base64 32),$(openssl rand -base64 32),$(openssl rand -base64 32),$(openssl rand -base64 32)"
export API_TOKEN_SALT="$(openssl rand -base64 32)"
export ADMIN_JWT_SECRET="$(openssl rand -base64 32)"
export ADMIN_ENCRYPTION_KEY="$(openssl rand -base64 32)"
export TRANSFER_TOKEN_SALT="$(openssl rand -base64 32)"
export JWT_SECRET="$(openssl rand -base64 32)"
SECRETS
fi
source /app/data/.secrets

echo "==&gt; Mapping database environment..."
export DATABASE_CLIENT=postgres
export DATABASE_HOST="${CLOUDRON_POSTGRESQL_HOST}"
export DATABASE_PORT="${CLOUDRON_POSTGRESQL_PORT}"
export DATABASE_NAME="${CLOUDRON_POSTGRESQL_DATABASE}"
export DATABASE_USERNAME="${CLOUDRON_POSTGRESQL_USERNAME}"
export DATABASE_PASSWORD="${CLOUDRON_POSTGRESQL_PASSWORD}"
export DATABASE_SSL=false

export PORT=8000
export HOST=0.0.0.0
export NODE_ENV=production
export STRAPI_TELEMETRY_DISABLED=true

echo "==&gt; Starting Strapi on port 8000..."
exec /usr/local/bin/gosu cloudron:cloudron /app/code/node_modules/.bin/strapi start
</code></pre>
<blockquote>
<p dir="auto"><strong>Gotcha #12 — Cryptographic secrets must survive restarts.</strong> Strapi's <code>APP_KEYS</code>, <code>ADMIN_JWT_SECRET</code>, etc. are used to sign sessions and tokens. If they regenerate on every container restart, all admin sessions are invalidated. Generate them once on first boot and persist to <code>/app/data/.secrets</code>.</p>
<p dir="auto"><strong>Gotcha #13 — <code>CLOUDRON_POSTGRESQL_*</code> vars change on every restart.</strong> Never hardcode them. Always read from environment in <code>start.sh</code> and re-export to Strapi's expected variable names.</p>
<p dir="auto"><strong>Gotcha #14 — Run Strapi as the <code>cloudron</code> user, not root.</strong> The <code>start.sh</code> script runs as root (needed for <code>mkdir</code>), but always use <code>gosu cloudron:cloudron</code> to drop privileges before the app process starts.</p>
<p dir="auto"><strong>Gotcha #15 — Call the binary directly, not via <code>node</code>.</strong> pnpm creates shell wrapper scripts in <code>node_modules/.bin/</code>. Calling <code>node node_modules/.bin/strapi</code> passes a shell script to the Node.js interpreter, causing a <code>SyntaxError: missing ) after argument list</code>. Call it directly: <code>/app/code/node_modules/.bin/strapi start</code>.</p>
</blockquote>
<p dir="auto"><strong><code>.dockerignore</code></strong></p>
<pre><code>.git
.gitignore
*.md
strapi-app/.tmp
strapi-app/node_modules
strapi-app/build
strapi-app/.cache
strapi-app/.pnpm-store
</code></pre>
<hr />
<h2>Step 5 — Build the Docker image locally</h2>
<blockquote>
<p dir="auto"><strong>Why build locally?</strong> Cloudron's server-side build (<code>cloudron install</code> without <code>--image</code>) uploads a source archive and builds on the server. For Strapi, the <code>node_modules</code> + admin panel build produces an archive too large (~700 MB) for the upload timeout. Building locally and pushing to a registry bypasses this entirely.</p>
</blockquote>
<pre><code class="language-bash">cd ~/projects/strapi-cloudron

# Build (takes 10-15 mins on first run — better-sqlite3 compiles native C++)
podman build --no-cache -t docker.io/YOURUSERNAME/strapi-cloudron:5.38.0 .

# Push to Docker Hub
podman push docker.io/YOURUSERNAME/strapi-cloudron:5.38.0
</code></pre>
<blockquote>
<p dir="auto"><strong>Tip:</strong> The <code>--no-cache</code> flag ensures a clean build. Omit it on subsequent builds to use layer caching and speed things up (only use <code>--no-cache</code> when you change <code>package.json</code> or the Dockerfile itself).</p>
</blockquote>
<hr />
<h2>Step 6 — Install on Cloudron</h2>
<pre><code class="language-bash">cloudron install \
  --image docker.io/YOURUSERNAME/strapi-cloudron:5.38.0 \
  --location cms.yourdomain.com
</code></pre>
<p dir="auto">Tail logs while it starts (first boot runs DB migrations — allow ~60 seconds):</p>
<pre><code class="language-bash">cloudron logs -f --app cms.yourdomain.com
</code></pre>
<p dir="auto">A successful start looks like:</p>
<pre><code>==&gt; Creating runtime directories...
==&gt; Generating secrets for first run...
==&gt; Mapping database environment...
==&gt; Starting Strapi on port 8000...
[2026-03-05 13:04:59.990] info: Strapi started successfully
[2026-03-05 13:05:00.170] http: GET /_health (29 ms) 204
</code></pre>
<hr />
<h2>Step 7 — Create your admin account</h2>
<p dir="auto">Visit <code>https://cms.yourdomain.com/admin</code> and register your first administrator. This must be done manually on first install — Cloudron's no-setup-screen requirement doesn't apply here since Strapi's first-run setup is minimal (just a name, email, and password).</p>
<hr />
<h2>Updating Strapi</h2>
<p dir="auto">When a new Strapi version is released, update <code>package.json</code>, rebuild and push with a new tag, then:</p>
<pre><code class="language-bash">cloudron update \
  --image docker.io/YOURUSERNAME/strapi-cloudron:NEW_VERSION \
  --app cms.yourdomain.com
</code></pre>
<p dir="auto">Cloudron automatically backs up the app (including the PostgreSQL database) before updating.</p>
<hr />
<h2>Path B — Packaging for the Official Cloudron App Store</h2>
<p dir="auto">If you want to submit Strapi to the App Store, additional steps are required beyond a working personal installation:</p>
<p dir="auto"><strong>1. Use a proper manifest ID</strong><br />
The <code>id</code> field must be a reverse-domain identifier: <code>io.strapi.cloudronapp</code> — coordinate with the Cloudron team on naming before submitting.</p>
<p dir="auto"><strong>2. Add a <code>checklist</code> for post-install guidance</strong></p>
<pre><code class="language-json">"checklist": {
  "first-admin": {
    "sso": false,
    "message": "Create your first administrator at /admin"
  }
}
</code></pre>
<p dir="auto"><strong>3. Add an icon</strong><br />
Place a 256×256 PNG at <code>logo.png</code> in your package root.</p>
<p dir="auto"><strong>4. Write browser tests</strong><br />
Cloudron requires automated install/backup/restore/update tests. See existing packages at <a href="https://git.cloudron.io/cloudron" target="_blank" rel="noopener noreferrer nofollow ugc">git.cloudron.io/cloudron</a> for examples.</p>
<p dir="auto"><strong>5. Post in the App Wishlist forum first</strong><br />
Before packaging, leave a note in the <a href="https://forum.cloudron.io">App Wishlist</a> category. The community may have already started, and the Cloudron team can advise on naming and requirements before you invest time.</p>
<p dir="auto"><strong>6. Host the packaging code in its own repo</strong><br />
Keep the Cloudron packaging repo (Dockerfile, manifest, <a href="http://start.sh" target="_blank" rel="noopener noreferrer nofollow ugc">start.sh</a>) separate from the Strapi app code repo.</p>
<p dir="auto"><strong>7. Open Source licence required</strong><br />
The packaging code must be MIT, GPL, BSD or similar. Your Strapi content and configuration can remain private.</p>
<hr />
<h2>Complete Gotcha Reference</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>#</th>
<th>Problem</th>
<th>Solution</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Podman OCI SHELL warning</td>
<td>Add <code># syntax=docker/dockerfile:1</code> or use <code>--format docker</code></td>
</tr>
<tr>
<td>2</td>
<td>CLI runs on wrong machine</td>
<td>Always run <code>cloudron</code> commands locally, not on server</td>
</tr>
<tr>
<td>3</td>
<td><code>@strapi/plugin-i18n@^5.0.0</code> not found</td>
<td>Package absorbed into core — remove from dependencies</td>
</tr>
<tr>
<td>4</td>
<td>Missing admin peer deps at build time</td>
<td>Add <code>react</code>, <code>react-dom</code>, <code>react-router-dom</code>, <code>styled-components</code> to <code>package.json</code></td>
</tr>
<tr>
<td>5</td>
<td>Wrong media URLs</td>
<td>Set <code>url: env('CLOUDRON_APP_ORIGIN')</code> in <code>config/server.js</code></td>
</tr>
<tr>
<td>6</td>
<td>Health check fails</td>
<td>Use <code>/_health</code> (returns 204), not <code>/</code></td>
</tr>
<tr>
<td>7</td>
<td>OOM silent kill</td>
<td>Set <code>memoryLimit: 1073741824</code> (1 GB) in manifest</td>
</tr>
<tr>
<td>8</td>
<td>Read-only filesystem crashes</td>
<td>Symlink <code>.tmp</code>, <code>public/uploads</code>, <code>database</code> to <code>/run</code> or <code>/app/data</code></td>
</tr>
<tr>
<td>9</td>
<td>Health check timeout on slow start</td>
<td>Build admin panel in Dockerfile, not <code>start.sh</code></td>
</tr>
<tr>
<td>10</td>
<td>pnpm blocks native module builds</td>
<td>Run <code>pnpm approve-builds</code> or <code>--yes</code> in Dockerfile</td>
</tr>
<tr>
<td>11</td>
<td>Wrong Node.js version</td>
<td>Install Node 22 LTS via NodeSource explicitly in Dockerfile</td>
</tr>
<tr>
<td>12</td>
<td>Sessions invalidated on restart</td>
<td>Persist secrets to <code>/app/data/.secrets</code>, generate once</td>
</tr>
<tr>
<td>13</td>
<td>DB connection fails after restart</td>
<td>Map <code>CLOUDRON_POSTGRESQL_*</code> vars in <code>start.sh</code> every time</td>
</tr>
<tr>
<td>14</td>
<td>Running as root</td>
<td>Use <code>gosu cloudron:cloudron</code> before exec</td>
</tr>
<tr>
<td>15</td>
<td><code>SyntaxError: missing ) after argument list</code></td>
<td>Don't call <code>node node_modules/.bin/strapi</code> — call the binary directly</td>
</tr>
<tr>
<td>16</td>
<td>Upload timeout on large source archives</td>
<td>Build locally with podman/docker, push to registry, use <code>--image</code> flag</td>
</tr>
</tbody>
</table>
<hr />
<h2>Final file listing</h2>
<pre><code>strapi-cloudron/
├── .dockerignore
├── CloudronManifest.json
├── Dockerfile
├── start.sh
└── strapi-app/
    ├── package.json
    ├── config/
    │   ├── admin.js
    │   ├── database.js
    │   ├── server.js
    │   └── env/
    │       └── production/
    ├── src/
    │   └── api/
    └── public/
        └── uploads/
</code></pre>
<hr />
<p dir="auto"><em>Guide written March 2026. Tested on Strapi v5.38.0, Cloudron 8.3, base image 5.0.0 (Ubuntu 24.04), Node.js 22.14.0, pnpm 10.30.2, Podman on Bazzite KDE.</em></p>
<p dir="auto"><em>If you run into issues not covered here, post in this thread and we'll update the guide.</em></p>
]]></description><link>https://forum.cloudron.io/topic/15183/how-to-package-and-deploy-strapi-v5-as-a-custom-app-on-cloudron</link><generator>RSS for Node</generator><lastBuildDate>Fri, 14 Aug 2026 06:29:58 GMT</lastBuildDate><atom:link href="https://forum.cloudron.io/topic/15183.rss" rel="self" type="application/rss+xml"/><pubDate>Thu, 05 Mar 2026 13:50:15 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to How to Package and Deploy Strapi v5 as a Custom App on Cloudron on Thu, 19 Mar 2026 14:48:24 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/loudlemur" aria-label="Profile: LoudLemur">@<bdi>LoudLemur</bdi></a> i'm working a lot with strapi, developed plugins and more <img src="https://forum.cloudron.io/assets/plugins/nodebb-plugin-emoji/emoji/android/1f61b.png?v=090c346130c" class="not-responsive emoji emoji-android emoji--stuck_out_tongue" style="height:23px;width:auto;vertical-align:middle" title=":P" alt="😛" /> So yeah, it def. has its limitations or "weird edges", but in general i'm happy with it as backend for my apps. And like you said, strapi is more for serious business depending logic (with more effort in CI/CD) while directus is more for non-technical and quick-deploy setups.</p>
]]></description><link>https://forum.cloudron.io/post/122191</link><guid isPermaLink="true">https://forum.cloudron.io/post/122191</guid><dc:creator><![CDATA[fanvyr]]></dc:creator><pubDate>Thu, 19 Mar 2026 14:48:24 GMT</pubDate></item><item><title><![CDATA[Reply to How to Package and Deploy Strapi v5 as a Custom App on Cloudron on Thu, 19 Mar 2026 07:59:08 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/fanvyr" aria-label="Profile: fanvyr">@<bdi>fanvyr</bdi></a> fair point, and I'll hold my hands up on both counts.<br />
On the MongoDB thing: you're right, I was working from stale information. Strapi dropped NoSQL support a while back. Corrected.<br />
On the read-only filesystem critique: I think I framed this wrong. After sitting with your reply and looking at this more carefully, the Content-Type Builder being unavailable in production isn't a Cloudron constraint being imposed on Strapi — it's Strapi's own documented stance. They explicitly discourage using the Content-Type Builder in production regardless of your hosting environment, because schema changes belong in version control and should be deployed deliberately. Cloudron's immutable container model is actually aligned with that philosophy, not fighting it.<br />
The real workflow is:</p>
<p dir="auto">Develop and modify content types locally in dev mode<br />
Schema files land in src/api/ in your Git repo<br />
Rebuild your Docker image with the updated schemas baked in<br />
cloudron update --image to deploy</p>
<p dir="auto">That's not a workaround, it's just treating your CMS schema like code, which is the right call when business logic depends on it.<br />
Where I think the "unsuitable" framing has some merit is for a narrower use case: someone who wants a fully self-contained admin experience where non-technical editors can modify the data model directly in the browser in production, with no local dev environment involved. For that specific workflow, Directus (schema-in-database) is genuinely a better fit on Cloudron. But that's a workflow preference, not a fundamental incompatibility.<br />
So to revise my original position: Strapi on Cloudron is well-suited for teams who treat their CMS like code, schemas in Git, changes shipped via image builds, proper dev/staging/prod cycle. If that matches how you work, it's a great fit. If you want point-and-click schema changes in production with no local tooling, look at Directus.<br />
Thanks for the correction!</p>
]]></description><link>https://forum.cloudron.io/post/122166</link><guid isPermaLink="true">https://forum.cloudron.io/post/122166</guid><dc:creator><![CDATA[LoudLemur]]></dc:creator><pubDate>Thu, 19 Mar 2026 07:59:08 GMT</pubDate></item><item><title><![CDATA[Reply to How to Package and Deploy Strapi v5 as a Custom App on Cloudron on Wed, 18 Mar 2026 23:36:40 GMT]]></title><description><![CDATA[<p dir="auto">To be honest, the only tricky part (in general for cloudron apps) are the env vars. When updating an app, always make sure to FIRST set the new env vars (it only works when the app is running... and if the app depends on a var to start, well.... ) and THEN update the container. I'm using Buddy selfhosted as my CI/CD and build a template which i can just duplicate for every cloudron app.<br />
Push to git -&gt; build image -&gt; push image -&gt; set env vars on cloudron -&gt; update cloudron</p>
]]></description><link>https://forum.cloudron.io/post/122155</link><guid isPermaLink="true">https://forum.cloudron.io/post/122155</guid><dc:creator><![CDATA[fanvyr]]></dc:creator><pubDate>Wed, 18 Mar 2026 23:36:40 GMT</pubDate></item><item><title><![CDATA[Reply to How to Package and Deploy Strapi v5 as a Custom App on Cloudron on Wed, 18 Mar 2026 23:32:02 GMT]]></title><description><![CDATA[<blockquote>
<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/fbartels" aria-label="Profile: fbartels">@<bdi>fbartels</bdi></a> <a href="/post/122153">said</a>:</p>
<blockquote>
<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/fanvyr" aria-label="Profile: fanvyr">@<bdi>fanvyr</bdi></a> <a href="/post/122128">said</a>:</p>
<p dir="auto">For a Cloudron app that means building a new image, push it and update your app. No magic. (And for sure no complicated strapi transfer workaround needed....).</p>
</blockquote>
<p dir="auto">Maybe what is then needed is a kind of example or started git project that one can fork and add their own personal website on top of this fork. Maybe add example github/gitea actions to it.</p>
</blockquote>
<p dir="auto">Why that, the original post here covers it pretty much with Step 5 (building the image and pushing) and Step 6 (instead of install use update...)</p>
<p dir="auto">Important: Use (Docker) Container Versioning for your custom strapi-cloudron-app.</p>
]]></description><link>https://forum.cloudron.io/post/122154</link><guid isPermaLink="true">https://forum.cloudron.io/post/122154</guid><dc:creator><![CDATA[fanvyr]]></dc:creator><pubDate>Wed, 18 Mar 2026 23:32:02 GMT</pubDate></item><item><title><![CDATA[Reply to How to Package and Deploy Strapi v5 as a Custom App on Cloudron on Wed, 18 Mar 2026 23:00:26 GMT]]></title><description><![CDATA[<blockquote>
<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/fanvyr" aria-label="Profile: fanvyr">@<bdi>fanvyr</bdi></a> <a href="/post/122128">said</a>:</p>
<p dir="auto">For a Cloudron app that means building a new image, push it and update your app. No magic. (And for sure no complicated strapi transfer workaround needed....).</p>
</blockquote>
<p dir="auto">Maybe what is then needed is a kind of example or started git project that one can fork and add their own personal website on top of this fork. Maybe add example github/gitea actions to it.</p>
]]></description><link>https://forum.cloudron.io/post/122153</link><guid isPermaLink="true">https://forum.cloudron.io/post/122153</guid><dc:creator><![CDATA[fbartels]]></dc:creator><pubDate>Wed, 18 Mar 2026 23:00:26 GMT</pubDate></item><item><title><![CDATA[Reply to How to Package and Deploy Strapi v5 as a Custom App on Cloudron on Wed, 18 Mar 2026 15:59:41 GMT]]></title><description><![CDATA[<blockquote>
<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/loudlemur" aria-label="Profile: LoudLemur">@<bdi>LoudLemur</bdi></a> <a href="/post/121306">said</a>:</p>
<p dir="auto">Strapi: Strapi (Node.js headless CMS) loves the latest Node 24, MongoDB 8, and Redis.</p>
</blockquote>
<p dir="auto">Not true. Strapi dropped NoSQL DB Support. <a href="https://docs.strapi.io/cms/configurations/database" target="_blank" rel="noopener noreferrer nofollow ugc">Link</a>.</p>
<blockquote>
<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/loudlemur" aria-label="Profile: LoudLemur">@<bdi>LoudLemur</bdi></a> <a href="/post/121547">said</a>:</p>
<p dir="auto">The Cloudron Strapi package runs with a read-only /app/code filesystem. This means the Content-Type Builder is permanently unusable in production mode, and there is no supported way to deploy custom content types to it. Schema files cannot be written via transfer, push, or direct filesystem access.<br />
The only partial workaround is: build content types locally in dev mode → use strapi transfer --exclude files to push to production. However this only works if the schemas already exist on the destination, which they cannot, making it a circular problem.</p>
</blockquote>
<p dir="auto">This is not "because of running in read-only filesystem" - its by design. <a href="https://docs.strapi.io/cms/faq#why-cant-i-create-or-update-content-types-in-productionstaging" target="_blank" rel="noopener noreferrer nofollow ugc">Link</a>.</p>
<p dir="auto">To manage Collections or Content-Types, you <strong>always</strong> have to build in local dev setup and push the changes. For a Cloudron app that means building a new image, push it and update your app. No magic. (And for sure no complicated strapi transfer workaround needed....).</p>
<p dir="auto">Which, to be honest, IS ALWAYS a good idea, because you're messing with the Database... Even worse, a prod one in that case <img src="https://forum.cloudron.io/assets/plugins/nodebb-plugin-emoji/emoji/android/1f609.png?v=090c346130c" class="not-responsive emoji emoji-android emoji--wink" style="height:23px;width:auto;vertical-align:middle" title=";-)" alt="😉" /> (Like Directus...)</p>
<p dir="auto">So, (just my opinion...) i'd rather use Strapi with a dev / staging cycle and have clear easy migrations instead of a "heart operation" with directus... sure you can develop your directus upgrade locally, but then the CI/CD turns into hell...<br />
(This is of course assuming you don't mean some small self-project or no business logic depends on it. If not - Directus may actually a good choice.)</p>
]]></description><link>https://forum.cloudron.io/post/122128</link><guid isPermaLink="true">https://forum.cloudron.io/post/122128</guid><dc:creator><![CDATA[fanvyr]]></dc:creator><pubDate>Wed, 18 Mar 2026 15:59:41 GMT</pubDate></item><item><title><![CDATA[Reply to How to Package and Deploy Strapi v5 as a Custom App on Cloudron on Tue, 10 Mar 2026 08:58:52 GMT]]></title><description><![CDATA[<p dir="auto"><img src="https://forum.cloudron.io/assets/plugins/nodebb-plugin-emoji/emoji/android/26a0.png?v=090c346130c" class="not-responsive emoji emoji-android emoji--warning" style="height:23px;width:auto;vertical-align:middle" title="⚠" alt="⚠" />️ Critical limitation: The Cloudron Strapi package runs with a read-only /app/code filesystem. This means the Content-Type Builder is permanently unusable in production mode, and there is no supported way to deploy custom content types to it. Schema files cannot be written via transfer, push, or direct filesystem access.<br />
The only partial workaround is: build content types locally in dev mode → use strapi transfer --exclude files to push to production. However this only works if the schemas already exist on the destination, which they cannot,  making it a circular problem.<br />
Directus (also available on Cloudron) stores its schema in the database rather than the filesystem, making it fully compatible with Cloudron's immutable package model.</p>
]]></description><link>https://forum.cloudron.io/post/121547</link><guid isPermaLink="true">https://forum.cloudron.io/post/121547</guid><dc:creator><![CDATA[LoudLemur]]></dc:creator><pubDate>Tue, 10 Mar 2026 08:58:52 GMT</pubDate></item><item><title><![CDATA[Reply to How to Package and Deploy Strapi v5 as a Custom App on Cloudron on Thu, 05 Mar 2026 22:38:32 GMT]]></title><description><![CDATA[<p dir="auto">Would have probably been easier to update the old attempt <img src="https://forum.cloudron.io/assets/plugins/nodebb-plugin-emoji/emoji/android/1f609.png?v=090c346130c" class="not-responsive emoji emoji-android emoji--wink" style="height:23px;width:auto;vertical-align:middle" title=";)" alt="😉" /> <a href="https://git.cloudron.io/msbt/strapi-app" target="_blank" rel="noopener noreferrer nofollow ugc">https://git.cloudron.io/msbt/strapi-app</a></p>
]]></description><link>https://forum.cloudron.io/post/121317</link><guid isPermaLink="true">https://forum.cloudron.io/post/121317</guid><dc:creator><![CDATA[msbt]]></dc:creator><pubDate>Thu, 05 Mar 2026 22:38:32 GMT</pubDate></item><item><title><![CDATA[Reply to How to Package and Deploy Strapi v5 as a Custom App on Cloudron on Thu, 05 Mar 2026 18:15:02 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/jdaviescoates" aria-label="Profile: jdaviescoates">@<bdi>jdaviescoates</bdi></a></p>
<p dir="auto">Strapi: Strapi (Node.js headless CMS) loves the latest Node 24, MongoDB 8, and Redis. The custom build feature makes it dead simple to package Strapi + custom plugins, database migrations, and even a Svelte/Astro frontend in one app. You get Cloudron’s one-click updates, backups (including DB), SSO, domains, and isolation — while still running the very latest Strapi version with your own custom admin UI or API extensions. Community Strapi packages have existed in the past; now they’re officially encouraged and auto-updating.</p>
<p dir="auto">Main Improvements &amp; Benefits in 9.1 / 9.1.2<br />
These focus on making Cloudron far more developer-friendly for custom and modern web apps:</p>
<p dir="auto">Custom app build &amp; deploy (biggest new feature): You can now git clone any app package repository and run cloudron install directly on your server. Cloudron builds the Docker image on-the-fly from source. Your app’s full source code is included in backups and can be rebuilt/restored automatically. No more manual Docker packaging or third-party tools needed.<br />
Community Apps: Anyone can publish apps via a simple CloudronVersions.json file hosted anywhere (GitHub, GitLab, etc.). Install them with one click from any URL, and Cloudron tracks the upstream repo for automatic updates. This opens up the ecosystem massively — no waiting for official Cloudron approval.<br />
Updated runtimes:<br />
Node.js 24.x (huge for modern JS/TS stacks)<br />
MongoDB 8<br />
Redis 8.4</p>
<p dir="auto">UX &amp; operational wins:<br />
Much better progress reporting (percentage complete + elapsed/estimated time) for backups and app installs.<br />
Improved notifications view, event log, and backup verification UI.<br />
Faster backups (up to 100× in some cases, carried over from v9).</p>
<p dir="auto">Security:<br />
Native Passkey support (works with YubiKey, Bitwarden, browser passkeys, etc.).<br />
ACME ARI for better certificate management.</p>
<p dir="auto">Overall benefits: Easier self-hosting of custom apps, faster iteration, automatic updates/backups/SSL/domains/isolation, and dramatically reduced maintenance compared to raw Docker or VPS setups.</p>
]]></description><link>https://forum.cloudron.io/post/121306</link><guid isPermaLink="true">https://forum.cloudron.io/post/121306</guid><dc:creator><![CDATA[LoudLemur]]></dc:creator><pubDate>Thu, 05 Mar 2026 18:15:02 GMT</pubDate></item><item><title><![CDATA[Reply to How to Package and Deploy Strapi v5 as a Custom App on Cloudron on Thu, 05 Mar 2026 16:27:58 GMT]]></title><description><![CDATA[<blockquote>
<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/loudlemur" aria-label="Profile: LoudLemur">@<bdi>LoudLemur</bdi></a> <a href="/post/121295">said</a>:</p>
<p dir="auto">The latest updates in Cloudron really helped.</p>
</blockquote>
<p dir="auto">Presumably especially the Skills?</p>
]]></description><link>https://forum.cloudron.io/post/121303</link><guid isPermaLink="true">https://forum.cloudron.io/post/121303</guid><dc:creator><![CDATA[jdaviescoates]]></dc:creator><pubDate>Thu, 05 Mar 2026 16:27:58 GMT</pubDate></item><item><title><![CDATA[Reply to How to Package and Deploy Strapi v5 as a Custom App on Cloudron on Thu, 05 Mar 2026 15:28:18 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/timconsidine" aria-label="Profile: timconsidine">@<bdi>timconsidine</bdi></a> This one was largely Claude Opus 4.6 extended</p>
<p dir="auto">The latest updates in Cloudron really helped.</p>
]]></description><link>https://forum.cloudron.io/post/121295</link><guid isPermaLink="true">https://forum.cloudron.io/post/121295</guid><dc:creator><![CDATA[LoudLemur]]></dc:creator><pubDate>Thu, 05 Mar 2026 15:28:18 GMT</pubDate></item><item><title><![CDATA[Reply to How to Package and Deploy Strapi v5 as a Custom App on Cloudron on Thu, 05 Mar 2026 14:34:29 GMT]]></title><description><![CDATA[<p dir="auto">AI dev agent ? Which one ?<br />
Just interested, doesn’t seem familiar.</p>
]]></description><link>https://forum.cloudron.io/post/121290</link><guid isPermaLink="true">https://forum.cloudron.io/post/121290</guid><dc:creator><![CDATA[timconsidine]]></dc:creator><pubDate>Thu, 05 Mar 2026 14:34:29 GMT</pubDate></item></channel></rss>