Product-code half (fleet gate, capabilities endpoint, offline feature migration, PREMIUM_FEATURES completion) lives in the dashcaddy repo, branch dc/DC-165-uniformity — judged as one bundle. Site edits (all claims verified against product source, see EVIDENCE [9]): - template count 77 -> 78 EVERYWHERE (page.tsx, features, pricing, docs, blog bodies + manifest.ts frontmatter per three-way consistency rule, AppShowcase, DocsLayout, FaqSection). Source of truth: APP_TEMPLATES keys == 78. - internal-identifier leak sweep: 85 occurrences of *.sami (internal TLD), an internal Git host URL, a private venv path, and a server-local repo path in pricing comment -> replaced with the documented .home style / generic wording. status.sami replaced by dashcaddy.local (product default dashboardHost). Verified ZERO remaining in rebuilt out/. - advertise as Premium (owner decision 2026-10-09): pricing premium list + FAQ + docs/premium table + two new deep-dive sections (Workflow Automation: the five bundled ids from bundled-workflows.js; Scheduled Backups: schedule fields from the /backups/schedule schema, manual one-click + DR explicitly noted as free). - calendar widget + hosted-Radicale auto-wiring advertised (features page chip + new docs/catalog/radicale install guide following house template structure; syncthing page mirrored for the placeholder conventions). - Connect AI one-click key flow advertised (features chip). - MCP card softened: 'entire infrastructure' -> the real tool scope + pointer to GET /api/v1/capabilities (public ground-truth manifest). - llms.txt: capabilities endpoint pointer + 'prefer it over static prose' + MCP tool examples + premium gate list; robots.txt gains LLMs-Txt line. - NOTE: a parked unrelated work-in-progress (dc-seo-r8 blog edits) was stashed before the build and is NOT in this commit. Build gate: rm -rf .next out && npm run build -> clean, 123 static pages, out/ greps zero stale counts/leaks.
136 lines
9.7 KiB
TypeScript
136 lines
9.7 KiB
TypeScript
import Navbar from '@/components/Navbar';
|
||
import Footer from '@/components/Footer';
|
||
import DocsLayout from '@/components/docs/DocsLayout';
|
||
|
||
export const metadata = {
|
||
title: 'Install Immich — DashCaddy Docs',
|
||
description: 'Install and configure Immich via DashCaddy. Self-hosted Google Photos alternative',
|
||
};
|
||
|
||
export default function immichDocsPage() {
|
||
return (
|
||
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
|
||
<Navbar />
|
||
<DocsLayout
|
||
title="Install Immich"
|
||
intro="Self-hosted Google Photos alternative"
|
||
>
|
||
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
|
||
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Photos</span>
|
||
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Difficulty: Intermediate</span>
|
||
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">ghcr.io/immich-app/immich-server:latest</code></span>
|
||
</div>
|
||
|
||
<h2>What is Immich?</h2>
|
||
<p>Self-hosted Google Photos alternative</p>
|
||
<p>Immich ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Immich, not installing it.</p>
|
||
|
||
<h2>Prerequisites</h2>
|
||
<ul>
|
||
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://dashcaddy.local</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
|
||
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
|
||
<li>No special host paths required.</li>
|
||
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
|
||
</ul>
|
||
|
||
<h2>Install via the DashCaddy dashboard</h2>
|
||
<ol>
|
||
<li>Sign in at <code>https://dashcaddy.local</code> (or your host's dashboard URL).</li>
|
||
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
|
||
<li>Pick <strong>Immich</strong> from the Photos category.</li>
|
||
<li>Fill in the deployment form: subdomain (default suggestion: <code>photos</code>), host port (default: <code>2283</code>).</li>
|
||
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/api/server-info/ping</code>) to pass.</li>
|
||
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>{success, containerId, url, message, setupInstructions}</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
|
||
</ol>
|
||
|
||
<h2>Install via the REST API</h2>
|
||
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer <jwt></code>.</p>
|
||
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://dashcaddy.local/api/v1/apps/deploy \\
|
||
-H "X-API-Key: dk_your_api_key" \\
|
||
-H "Content-Type: application/json" \\
|
||
-d '{
|
||
"appId": "immich",
|
||
"config": {
|
||
"subdomain": "photos",
|
||
"port": 2283
|
||
}
|
||
}'</code></pre>
|
||
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
|
||
<ul>
|
||
<li><code>config.port</code> — host port (1–65535). Defaults to the template's <code>defaultPort</code>.</li>
|
||
<li><code>config.mediaPath</code> — host directory to mount as the media library.</li>
|
||
<li><code>config.plexClaimToken</code> — Plex claim token (when the upstream service needs one).</li>
|
||
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> — attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
|
||
<li><code>config.tailscaleOnly: true</code> — restrict the reverse-proxy entry to your Tailscale network.</li>
|
||
<li><code>config.allowedIPs</code> — array of CIDR ranges allowed past the reverse proxy.</li>
|
||
<li><code>config.createDns: false</code> — skip DNS record creation (use when the subdomain already resolves).</li>
|
||
<li><code>config.resources</code> — <code>{memory, cpus}</code> limits applied to the container.</li>
|
||
</ul>
|
||
|
||
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
|
||
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
|
||
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://dashcaddy.local/api/v1/ai/intent \\
|
||
-H "X-API-Key: dk_your_api_key" \\
|
||
-H "Content-Type: application/json" \\
|
||
-d '{ "message": "Deploy Immich on my home host and expose it at photos.home" }'</code></pre>
|
||
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> — your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
|
||
|
||
<h2>Install via the MCP Server</h2>
|
||
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
|
||
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://dashcaddy.local:3001 # internal API URL, may differ from dashboard URL
|
||
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
|
||
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull ghcr.io/immich-app/immich-server:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
|
||
|
||
<h2>Post-install: first-run checklist</h2>
|
||
<ol>
|
||
<li>Requires PostgreSQL and Redis</li>
|
||
<li>Install mobile apps for backup</li>
|
||
<li>Configure machine learning for face detection</li>
|
||
</ol>
|
||
<h2>Volumes and persistent data</h2>
|
||
<p>DashCaddy creates these volume mounts in the container spec:</p>
|
||
<ul>
|
||
<li><code>/opt/immich/upload:/usr/src/app/upload</code></li>
|
||
<li><code>/opt/immich/library:/usr/src/app/library</code></li>
|
||
</ul>
|
||
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
|
||
|
||
<h2>Environment variables</h2>
|
||
<ul>
|
||
<li><code>DB_HOSTNAME</code></li>
|
||
<li><code>DB_USERNAME</code></li>
|
||
<li><code>DB_PASSWORD</code></li>
|
||
<li><code>DB_DATABASE_NAME</code></li>
|
||
<li><code>REDIS_HOSTNAME</code></li>
|
||
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload — to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
|
||
|
||
<h2>Updating the image</h2>
|
||
<p>There is no built-in auto-update — DashCaddy does NOT poll for new image digests. To pull a new version:</p>
|
||
<ol>
|
||
<li>SSH into the DashCaddy host and run <code>docker pull ghcr.io/immich-app/immich-server:latest</code>.</li>
|
||
<li>Restart the container: <code>docker restart <containerId></code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
|
||
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
|
||
</ol>
|
||
|
||
<h2>Backups</h2>
|
||
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> — the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/{appId}/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
|
||
|
||
<h2>Troubleshooting</h2>
|
||
<p>Common issues with Immich:</p>
|
||
<ul>
|
||
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
|
||
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
|
||
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/api/server-info/ping</code> is not returning 200. Inspect <code>docker logs <containerId></code> directly.</li>
|
||
</ul>
|
||
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
|
||
|
||
<hr className="my-8 border-surface-700" />
|
||
<p className="text-sm text-surface-400">
|
||
Template ID: <code>immich</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
|
||
</p>
|
||
</DocsLayout>
|
||
<Footer />
|
||
</div>
|
||
);
|
||
}
|