{"result":{"depiction_url":null,"edges":{"relation":{"objects":[{"created":"2026-10-07T11:13:15Z","object_id":{"id":3069,"is_a":["text","documentation","adminguide"],"name":"admin_media_storage","title":{"_type":"trans","tr":{"en":"Configure and check persistent media storage"}},"uri":"https:\/\/zotonic.com\/id\/3069"},"seq":1},{"created":"2026-10-07T11:13:15Z","object_id":{"id":3082,"is_a":["text","documentation","adminguide"],"name":"admin_media_failures","title":{"_type":"trans","tr":{"en":"Diagnose failed uploads or missing media"}},"uri":"https:\/\/zotonic.com\/id\/3082"},"seq":2},{"created":"2026-10-07T11:13:15Z","object_id":{"id":3065,"is_a":["text","documentation","adminguide"],"name":"admin_persistent_settings","title":{"_type":"trans","tr":{"en":"Change persistent configuration"}},"uri":"https:\/\/zotonic.com\/id\/3065"},"seq":3},{"created":"2026-10-07T11:13:15Z","object_id":{"id":3078,"is_a":["text","documentation","adminguide"],"name":"admin_health_checks","title":{"_type":"trans","tr":{"en":"Check that a site is working"}},"uri":"https:\/\/zotonic.com\/id\/3078"},"seq":4},{"created":"2026-10-07T11:13:15Z","object_id":{"id":3066,"is_a":["text","documentation","adminguide"],"name":"admin_hostnames_https","title":{"_type":"trans","tr":{"en":"Configure hostnames and HTTPS"}},"uri":"https:\/\/zotonic.com\/id\/3066"},"seq":5}],"predicate":{"id":303,"is_a":["meta","predicate"],"name":"relation","title":{"_type":"trans","tr":{"nl":"Relatie","en":"Relation"}},"uri":"http:\/\/purl.org\/dc\/terms\/relation"}},"subject":{"objects":[{"created":"2026-10-07T11:13:15Z","object_id":{"id":2554,"is_a":["categorization","keyword","keyword_information_type"],"name":"zotonic_topic_how_to_guide","title":"How-to guide","uri":"https:\/\/zotonic.com\/id\/2554"},"seq":1},{"created":"2026-10-07T11:13:15Z","object_id":{"id":2566,"is_a":["categorization","keyword","keyword_audience"],"name":"zotonic_topic_operator","title":"Operator","uri":"https:\/\/zotonic.com\/id\/2566"},"seq":2},{"created":"2026-10-07T11:13:15Z","object_id":{"id":2576,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_media_management","title":"Media management","uri":"https:\/\/zotonic.com\/id\/2576"},"seq":3},{"created":"2026-10-07T11:13:15Z","object_id":{"id":2685,"is_a":["categorization","keyword","keyword_quality"],"name":"zotonic_topic_security","title":"Security","uri":"https:\/\/zotonic.com\/id\/2685"},"seq":4},{"created":"2026-10-07T11:13:15Z","object_id":{"id":2603,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_configuration","title":"Configuration","uri":"https:\/\/zotonic.com\/id\/2603"},"seq":5},{"created":"2026-10-07T11:13:15Z","object_id":{"id":2599,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_api_and_integration","title":"API and integration","uri":"https:\/\/zotonic.com\/id\/2599"},"seq":6}],"predicate":{"id":308,"is_a":["meta","predicate"],"name":"subject","title":{"_type":"trans","tr":{"en":"Keyword"}},"uri":"http:\/\/purl.org\/dc\/elements\/1.1\/subject"}}},"id":3084,"is_a":["text","documentation","adminguide"],"links":[{"rel":"self","target":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/3084"},{"rel":"hub","target":"https:\/\/zotonic.com\/.zotonic\/websub"}],"medium":null,"medium_url":null,"name":"admin_media_sandbox","page_url":{"en":"https:\/\/zotonic.com\/docs\/3084\/configure-media-sandboxing-and-an-optional-media-runner","x-default":"https:\/\/zotonic.com\/docs\/3084\/configure-media-sandboxing-and-an-optional-media-runner"},"preview_url":null,"resource":{"body":{"_type":"trans","tr":{"en":"<p><strong>Access needed:<\/strong> Server configuration and deployment access; access to both installations when using a remote runner.<\/p>\n<aside><p>The optional <strong>mediarunner<\/strong> site moves processing to a separate service, which is useful for keeping expensive conversions away from the web server or providing a suitable processing host.<\/p><\/aside>\n<p>Media processing opens files supplied by users and runs tools such as ImageMagick, Ghostscript, and FFmpeg. The media sandbox limits what those commands can read, write, and access.<\/p>\n<table class=\"table\"><thead><tr><th>Arrangement<\/th><th>Use it when<\/th><\/tr><\/thead><tbody><tr><td>Local processing with the native sandbox<\/td><td>Your web host has the required tools and enough capacity. No separate runner is needed.<\/td><\/tr><tr><td>A separate mediarunner service<\/td><td>You want separate processing capacity, tool management, or hosts. It adds credentials, network transfers, callbacks, and another service to monitor.<\/td><\/tr><\/tbody><\/table>\n<p>Running the runner on the same machine does not move CPU or memory pressure off that machine. Neither arrangement replaces upload permissions, backups of originals, or installation-level resource limits. Only calls using the media-profile API receive these restrictions; ordinary OS commands are not automatically sandboxed.<\/p>\n<h2>1. Verify the local sandbox<\/h2>\n<p>From the Zotonic checkout, connect to the running node:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-sh\">bin\/zotonic shell\n<\/code><\/pre>\n<p>At the Erlang prompt:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-erlang\">z_exec:sandbox_status().\n<\/code><\/pre>\n<p>An <code>{ok, ...}<\/code> result confirms the enforcement probe succeeded on <strong>this host<\/strong>. It does not check every tool or asset path. Press Ctrl-C twice to detach the remote shell.<\/p>\n<p>The <code>exec_sandbox<\/code> setting defaults to <code>required<\/code>. Missing helpers or failed sandbox setup stop processing, and a failed media command is not automatically retried without protection. However, an unsupported OS or kernel logs a notice and continues without OS isolation. Check the actual probe result and deployment logs.<\/p>\n<p>Linux needs enabled Landlock ABI 3 or newer and libseccomp 2.5 or newer. Build with the required headers and development library, and deploy the native helper and its runtime dependencies. Container support depends on the host kernel and actual mounts as well as the image. macOS uses Seatbelt with different limits; Windows and BSD have no implemented native backend in this version. The <a href=\"https:\/\/github.com\/zotonic\/zotonic\/blob\/master\/doc\/technotes\/media-sandboxing.md\">sandbox technical notes<\/a> describe those differences.<\/p>\n<h2>2. Check real conversions<\/h2>\n<p>On acceptance, upload a small image, a representative video if enabled, and a PDF if your site&#39;s policy permits PDF processing. Confirm the generated preview or conversion and inspect failures in the logs. The sandbox blocks network access for the media command; it does not change ImageMagick&#39;s own policy or enable formats that policy forbids.<\/p>\n<p>If a custom font or tool is inaccessible, grant only its required paths in the trusted Zotonic application configuration. For example, an additional font directory can be granted to both image profiles:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-erlang\">{exec_sandbox_profiles, #{\n    imagemagick =&gt; #{read =&gt; [&quot;\/srv\/media-assets\/fonts&quot;]},\n    imagemagick_pdf =&gt; #{read =&gt; [&quot;\/srv\/media-assets\/fonts&quot;]}\n}}.\n<\/code><\/pre>\n<p>This is one configuration property; place it in the existing configuration structure. The directory must exist on the processing host. Profiles have separate grants; custom binaries can also need access to their libraries and configuration. Retest after applying a change. Do not disable sandboxing or grant an entire site directory merely to make a conversion succeed.<\/p>\n<h2>3. Decide whether to use mediarunner<\/h2>\n<p>Stay with local processing unless a separate runner solves a concrete capacity or operational problem. For a runner, plan:<\/p>\n<ol><li>A supported processing host with the required tools and verified sandbox enforcement.<\/li><li>An HTTPS endpoint and credentials issued by the runner, following its <a href=\"https:\/\/github.com\/zotonic\/mediarunner#readme\">installation documentation<\/a>.<\/li><li>Network access from Zotonic to the runner <strong>and back<\/strong> to each client site&#39;s canonical HTTPS callback address.<\/li><li>Capacity, temporary-file storage, monitoring, and a decision about local fallback.<\/li><\/ol>\n<p>The runner receives media inputs and returns processing results. Treat it as a trusted service handling those files, including private uploads. Use deployment-level CPU, memory, process, and disk controls: per-command limits do not bound the aggregate load of all jobs.<\/p>\n<h2>4. Install the runner and create a consumer<\/h2>\n<p>On the processing host, use compatible Zotonic and mediarunner revisions. The current runner and client require protocol version 3.<\/p>\n<ol><li>Place the mediarunner site in <code>apps_user\/mediarunner<\/code> in the runner&#39;s Zotonic checkout. Build the checkout with <code>make<\/code>. For a container deployment, follow the runner&#39;s <a href=\"https:\/\/github.com\/zotonic\/mediarunner\/blob\/master\/docs\/docker.md\">Docker build and deployment instructions<\/a>, which also cover persistent volumes and the service user.<\/li><li>Install the required media tools, fonts, ImageMagick policy, and sandbox helper. Verify enforcement on this processing host.<\/li><li>Configure the hostname, database\/schema, trusted HTTPS, and administrator password in private site configuration, such as <code>site_config.d\/mediarunner\/site.config<\/code> below the installation&#39;s configuration directory. The supplied site is disabled and contains no credentials. Enable it only after completing these settings, then start it through the installation&#39;s normal site-management procedure.<\/li><li>Open the runner&#39;s homepage and sign in as an administrator. Check the dashboard&#39;s sandbox status before sending files. An unsupported sandbox is shown as an alarm, not hidden behind a successful startup.<\/li><li>Select <strong>Add website \/ consumer<\/strong>, enter a recognizable client name, and create it.<\/li><li>Copy the generated OAuth2 key to the client&#39;s protected configuration. It is displayed only once. Use one consumer per client installation: cache access is isolated by consumer account, not by individual token.<\/li><li>Decide whether to restrict <code>mediarunner_callback_urls<\/code> in the runner&#39;s site configuration. The default accepts authenticated callers&#39; valid HTTPS endpoints; an explicit list permits only those exact endpoints, and an empty list disables callbacks.<\/li><\/ol>\n<p>The consumer flow creates a dedicated account with permission to use mediarunner, without administration access. API credentials authorize shell commands within the selected profile, so issue them only to trusted clients. Keep the runner&#39;s configuration and data persistent across deployments.<\/p>\n<h2>5. Configure the client<\/h2>\n<p>For a single runner, put these settings in the <strong><code>zotonic<\/code> application section of the client’s system <code>zotonic.config<\/code><\/strong>, not in the client site configuration:<\/p>\n<table class=\"table\"><thead><tr><th>Setting<\/th><th>Value<\/th><\/tr><\/thead><tbody><tr><td><code>media_runner_hostname<\/code><\/td><td>The runner hostname, with a port if needed<\/td><\/tr><tr><td><code>media_runner_protocol<\/code><\/td><td><code>https<\/code><\/td><\/tr><tr><td><code>media_runner_oauth2_key<\/code><\/td><td>The token issued for this client; keep it secret<\/td><\/tr><tr><td><code>media_runner_local_fallback<\/code><\/td><td><code>false<\/code> initially, so testing cannot silently use local processing<\/td><\/tr><\/tbody><\/table>\n<p>For several runners, <code>media_runners<\/code> supplies the pool instead of the single-runner settings. Inspect an existing pool before changing the single hostname: a configured pool takes precedence, and an empty pool selects local processing.<\/p>\n<p>The callback URL is generated from the client site&#39;s canonical configuration. The callback endpoint is supplied by <code>mod_base<\/code>; do not install the mediarunner site on every client. Check DNS, certificate trust, reverse-proxy routing, and request-size limits in both directions. For a cluster, callbacks must reach the submitting Zotonic node: pending registrations live in its memory.<\/p>\n<p>Keep the <code>file<\/code> utility installed on the client: MIME detection still runs locally. Apply client configuration using the normal configuration\/restart procedure. Drain or account for pending work before restarting the submitting node, because its pending callback registrations are lost.<\/p>\n<h2>6. Verify processing and failure behaviour<\/h2>\n<ol><li>With local fallback off, submit a representative image conversion and a video conversion if supported.<\/li><li>Confirm that the runner accepted and processed the jobs, that callbacks arrived, and that the final preview or output is usable on the client site.<\/li><li>In acceptance, make the runner temporarily unavailable and inspect the resulting failure and recovery. Keep production jobs out of this test.<\/li><li>Enable local fallback only if the client has the required tools, capacity, and verified sandbox policy. The fallback covers runner availability failures, not every processing or configuration error.<\/li><li>Restore normal service and check one new job before declaring the change complete.<\/li><\/ol>\n<p>An HTTP 204 callback acknowledgement means the callback was delivered, not that the conversion succeeded. Check the job result and installed output. A successful sandbox probe on the client also says nothing about enforcement on the runner: check each processing host.<\/p>\n<p>If a remote image job has no compatible runner, compare the configured ImageMagick major version and executable across the pool. For other failures, distinguish sandbox setup, tool policy, credentials, capacity, network delivery, and output validation before retrying.<\/p>\n<h2>7. Monitor capacity, cache, and credentials<\/h2>\n<p>Use the runner homepage for queue state, sandbox status, failures, and cache usage. Use <strong>Consumers → Statistics<\/strong> to investigate one client&#39;s workload. Heavy FFmpeg renders use a separate pool; video thumbnails and audio artwork share the general pool with image work.<\/p>\n<p>Configure runner capacity in its <strong>site configuration<\/strong>. Start with the automatic general-worker capacity and the default single FFmpeg render worker, then adjust from observed CPU, memory, queue delay, and disk use. HTTP 429 can mean overload or insufficient capacity. Raising a queue limit does not add processing capacity.<\/p>\n<p>Budget storage for both cached source\/results and private job files. They normally live below <code>&lt;data_dir&gt;\/sites\/mediarunner\/files\/<\/code> in <code>mediarunner\/<\/code> and <code>mediarunner-work\/<\/code>. PostgreSQL stores persistent jobs; interrupted processing can resume after a runner restart. This does not restore pending registrations lost by a restarted client node.<\/p>\n<p>After changing fonts, ImageMagick policy, or another dependency not detected automatically, increment the runner&#39;s <code>mediarunner_cache_version<\/code> so earlier rendered results are not reused. The processing cache is not the authoritative backup of client media originals.<\/p>\n<p>For credential rotation, open <strong>Consumers<\/strong>, edit the consumer, and select <strong>Generate a new OAuth2 key<\/strong>. Saving immediately revokes its existing keys. Copy the replacement to the client and verify a new job. Renaming without selecting that option keeps the current key. Deleting a consumer revokes its keys; existing jobs and cached files follow normal retention rather than being erased immediately.<\/p>\n<p>Drain outstanding jobs before upgrading Zotonic and mediarunner together. For exact settings, transfer limits, and reverse-proxy streaming requirements, use the runner&#39;s <a href=\"https:\/\/github.com\/zotonic\/mediarunner\/blob\/master\/docs\/reference.md\">configuration reference<\/a>.<\/p>"}},"category_id":{"id":2773,"is_a":["meta","category"],"name":"adminguide","title":"Administration guide","uri":"https:\/\/zotonic.com\/id\/adminguide"},"content_group_id":{"id":339,"is_a":["meta","content_group"],"name":"default_content_group","title":{"_type":"trans","tr":{"en":"Default Content Group"}},"uri":"https:\/\/zotonic.com\/id\/default_content_group"},"created":"2026-10-07T11:12:59Z","creator_id":{"id":1,"is_a":["person"],"name":"administrator","title":"Site Administrator","uri":"https:\/\/zotonic.com\/id\/1"},"doc_editorial_bundle":true,"doc_editorial_edges":{"relation":[3065,3066,3069,3078,3082],"subject":[2554,2566,2576,2599,2603,2685]},"is_authoritative":true,"is_dependent":false,"is_featured":false,"is_protected":false,"is_published":true,"is_unfindable":false,"language":["en"],"modified":"2026-10-07T11:13:17Z","modifier_id":{"id":1,"is_a":["person"],"name":"administrator","title":"Site Administrator","uri":"https:\/\/zotonic.com\/id\/1"},"name":"admin_media_sandbox","pivot_geocode":null,"pivot_location_lat":null,"pivot_location_lng":null,"privacy":0,"publication_end":"9999-06-01T00:00:00Z","publication_start":"2026-10-07T11:12:59Z","slug":"configure-media-sandboxing-and-an-optional-media-runner","summary":{"_type":"trans","tr":{"en":"Check media-process isolation and decide whether to run conversions on a separate service."}},"title":{"_type":"trans","tr":{"en":"Configure media sandboxing and an optional media runner"}},"title_slug":{"_type":"trans","tr":{"en":"configure-media-sandboxing-and-an-optional-media-runner"}},"tz":"UTC","uri":null,"version":26,"visible_for":0},"uri":"https:\/\/zotonic.com\/id\/3084","uri_template":"https:\/\/zotonic.com\/id\/:id","websub":{"hub":"https:\/\/zotonic.com\/.zotonic\/websub","topic":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/3084"}},"status":"ok"}