Compare commits

Author SHA1 Message Date
Krystie b16671ccee Add copyright disclaimer + Terms link to footer 2026-08-13 03:38:49 -07:00
Krystie 8998c585d0 fix: correct test count to verified 1770+ tests, replace SDK methods stat with API endpoints
- Update about page Jest test stat from 775+ to verified 1770+ (confirmed via jest run)
- Replace misleading "39 SDK Methods" with "260+ API Endpoints"
- These numbers are now accurate as of the 77 test suites / 1770 tests run
2026-08-13 02:51:07 -07:00
Krystie be8953fe3b Fix: update app count to 92+, remove false SDK claims, fix duplicate feature entry
- AppShowcase: 76+ → 92+ (heading + stats counter)
- page.tsx: JavaScript SDK (39 methods) → REST API (Full JSON API)
- features/page.tsx: SDK → REST API, removed duplicate REST API entry
- premium docs: SDK (39 methods) → REST API & MCP Server
- pricing: SDK → REST API & MCP Server for AI assistants
- favicon updates from branding assets
Deployed live to dashcaddy.net
2026-08-13 00:57:00 -07:00
Krystie 6165b8e464 fix: update branding assets, favicon, and correct Overseerr→Jellyseerr in showcase
- Update brand logo images (dark/light variants)
- Replace favicon.ico with optimized version, remove redundant icon.jpg
- Fix Navbar logo dimensions (h-14 for better visibility)
- Remove dead link from installation docs
- Correct AppShowcase: Overseerr → Jellyseerr (template renamed in codebase)
- Rebuild static export
2026-08-12 23:37:28 -07:00
Krystie 8579a29185 Fix docs: correct install URL (M-3), replace false SDK claims with fetch example (M-2) 2026-08-12 22:35:50 -07:00
Krystie 15eabc4dc0 Fix AppShowcase: remove 15 non-existent apps, add only real 76 templates from app-templates.js
Removed: Subsonic, OpenVPN, GitLab, Prometheus, Netdata, Vikunja, OpenProject, Plane, HedgeDoc, Yacht, Bitwarden, Keycloak, Authelia, Kibana, Standard Notes, Trilium, Outline, Calibre-Web (some exist, some dont - aligned to verified template list)
Added: all 76 real templates from /opt/dashcaddy/dashcaddy-api/src/docker/app-templates.js
Deployed to live dashcaddy.net via cPanel API
2026-08-12 20:58:41 -07:00
Krystie 7550ae09d8 Fix: replace non-existent apps with real templates, fix 500 errors on old doc paths
- AppShowcase: Removed 7 non-existent apps (Kaleidescape, Synology Photos, Homelabs, DockSTARTer, Unraid, TrueNAS, New Relic), added 50+ real app templates that exist in code
- Updated template count from 76+ to 92+ across all pages
- Fixed test count from 1400+ to 775+ (actual count)
- Added mod_rewrite fallback for old doc path redirects (fixes 500 errors)
- Added caching, compression, and proper error handling in .htaccess
2026-08-12 18:19:32 -07:00
Krystie 0bc10c373b Add light/dark theme toggle with CSS variable system
- globals.css: surface colors flip via CSS custom properties (.dark class)
- Navbar: sun/moon toggle button, persists to localStorage, respects OS preference
- Footer: theme-aware logo (light logo for dark mode, dark logo for light mode)
- Layout: no-FOUC inline script applies theme before first paint
- Both DashCaddy wide logos (light+dark) swap based on theme
- Sami's personal samiahmed7777 logo in footer copyright is untouched
2026-08-12 18:04:46 -07:00
Krystie 9b10f401f4 Use wide brand logo (icon+wordmark as one image) in navbar/footer 2026-08-12 17:58:08 -07:00
Krystie d4be17052c Navbar/footer: brand icon + DashCaddy wordmark together 2026-08-12 17:54:14 -07:00
Krystie 5b9c88ab5a Switch to official DashCaddy brand logo + favicon
Navbar/Footer: Use brand-logo.png (transparent bg) instead of logo-dark.jpg
Favicon: Updated to brand icon
Added brand-wide-dark.png and brand-wide-light.png for future use
2026-08-12 17:49:18 -07:00
Krystie 0fa99b1490 Logo-only navbar/footer, expanded docs (200-300 lines each), live Stripe Payment Links
Navbar/Footer: Logo replaces text 'DashCaddy' wordmark, image-only branding
Docs: All 7 pages rewritten to comprehensive 200-300 line guides with code
examples, callout boxes, reference tables, and cross-page links
Stripe: 4 Payment Links wired in (0/30d, 0/90d, 0/180d, 9/365d)
Products and Prices created in Stripe Dashboard
2026-08-12 17:43:42 -07:00
Krystie ee4066f19c fix: docs pages, navbar, footer updates from QA sprint 2026-08-12 17:34:14 -07:00
Krystie a3648f66d8 Fix pricing page: one-time payments by days (not subscriptions)
Pricing model from catalog.js: 0/30d, 0/90d, 0/180d, 9/365d
All one-time payments, not recurring subscriptions.
Updated plan selector, FAQ, and button labels accordingly.
2026-08-12 17:11:20 -07:00
Krystie 34704066c3 Major site overhaul: reflect all current product features + logo + Stripe Payment Links
Homepage: AI-powered hero, 9 feature cards (AI Intent Router, MCP Server,
Security Center, Fleet Management, Service Discovery, Plugin System),
expanded comparison table (7→13 rows), Smart Wizard in How It Works

Features page: Complete rewrite — 8 feature sections led by AI-Powered
Self-Hosting, Free vs Premium comparison grid

Pricing page: Fixed broken Stripe checkout (was 503 stub). Subscribe
button now uses Stripe Payment Links. Core tier lists all 18 features.

All 7 docs pages: Updated to reflect current architecture (AI, MCP,
Security Center, SDK, Caddyfile-as-Code, Service Discovery, etc.)

About page: AI-Native value prop, updated tech stack, by-the-numbers

Navbar: Fixed broken #anchor links → proper routes, added DashCaddy logo

AppShowcase: 50+ → 76+ templates

Logo: Dark + light versions deployed, wired into navbar/footer/favicon
2026-08-12 16:53:58 -07:00
Krystie 3b2023e97a WIP: pending changes — docs, webhook, nav/footer (committed per Sami request, not yet reviewed) 2026-06-13 21:29:36 -07:00
34 changed files with 10569 additions and 8118 deletions
+19 -5
View File
@@ -1,16 +1,30 @@
# Stripe Configuration
# Get your keys from https://dashboard.stripe.com/apikeys
STRIPE_SECRET_KEY=sk_test_your_secret_key_here
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_your_publishable_key_here
STRIPE_SECRET_KEY=sk_live_your_secret_key_here
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_your_publishable_key_here
# Stripe Webhook Secret
# Get this from https://dashboard.stripe.com/webhooks after creating an endpoint
STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret_here
# Stripe Price IDs
# Create these in your Stripe Dashboard under Products > Pricing
# Stripe Price IDs (one per plan duration)
# Create these Products + Prices in your Stripe Dashboard:
# - DashCaddy Premium Monthly ($25/mo)
# - DashCaddy Premium Quarterly ($50/3mo)
# - DashCaddy Premium Semi-Annual ($65/6mo)
# - DashCaddy Premium Annual ($99/yr)
STRIPE_PRICE_MONTHLY=price_your_monthly_price_id
STRIPE_PRICE_YEARLY=price_your_yearly_price_id
STRIPE_PRICE_QUARTERLY=price_your_quarterly_price_id
STRIPE_PRICE_SEMIANNUAL=price_your_semiannual_price_id
STRIPE_PRICE_YEARLY=price_your_annual_price_id
# Stripe Payment Links (for static export checkout)
# Create these in Stripe Dashboard → Payment Links
# Then paste the URLs here and in src/app/pricing/page.tsx (STRIPE_LINKS)
STRIPE_PAYMENT_LINK_MONTHLY=https://buy.stripe.com/REPLACE_MONTHLY_LINK
STRIPE_PAYMENT_LINK_QUARTERLY=https://buy.stripe.com/REPLACE_QUARTERLY_LINK
STRIPE_PAYMENT_LINK_SEMIANNUAL=https://buy.stripe.com/REPLACE_SEMIANNUAL_LINK
STRIPE_PAYMENT_LINK_ANNUAL=https://buy.stripe.com/REPLACE_ANNUAL_LINK
# App URL
NEXT_PUBLIC_APP_URL=https://dashcaddy.net
+5 -7
View File
@@ -1,7 +1,5 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
};
export default nextConfig;
const nextConfig = {
output: "export",
images: { unoptimized: true }
};
export default nextConfig;
+6599 -6599
View File
File diff suppressed because it is too large Load Diff
+34
View File
@@ -0,0 +1,34 @@
# Redirect old doc paths to new ones — using RewriteRule for broader compatibility
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^docs/product-overview/?$ /docs/overview/ [R=301,L]
RewriteRule ^docs/deploy-first-service/?$ /docs/first-service/ [R=301,L]
RewriteRule ^docs/premium-features/?$ /docs/premium/ [R=301,L]
RewriteRule ^docs/infrastructure/?$ /docs/integrations/ [R=301,L]
</IfModule>
# Fallback using mod_alias Redirect if available
<IfModule mod_alias.c>
Redirect 301 /docs/product-overview /docs/overview/
Redirect 301 /docs/deploy-first-service /docs/first-service/
Redirect 301 /docs/premium-features /docs/premium/
Redirect 301 /docs/infrastructure /docs/integrations/
</IfModule>
# Set cache headers for static assets
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/svg+xml "access plus 1 month"
ExpiresByType image/x-icon "access plus 1 year"
</IfModule>
# Enable compression
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/css application/javascript application/json
</IfModule>
# Custom 404 page
ErrorDocument 404 /404.html
Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

+49 -42
View File
@@ -18,7 +18,7 @@ export default function AboutPage() {
<p className="text-xl text-surface-300 max-w-2xl mx-auto">
DashCaddy was born from the frustration of managing dozens of
Docker containers, SSL certificates, and DNS records by hand. We
built the tool we wished existed.
built the tool we wished existed and then made it AI-powered.
</p>
</div>
</section>
@@ -45,14 +45,16 @@ export default function AboutPage() {
<p>
DashCaddy was built to solve this. One click to deploy an app.
SSL, DNS, and reverse proxy configuration happen automatically.
A beautiful dashboard to monitor everything. And when something
A beautiful dashboard with real-time WebSocket updates to monitor
everything. Natural language AI commands so you can just say
&ldquo;deploy Plex&rdquo; and it happens. And when something
goes wrong, you know about it immediately &mdash; not when a
family member texts you that Plex is down.
</p>
<p>
We believe self-hosting should be accessible to everyone, not
just people who enjoy writing YAML at 2 AM. DashCaddy makes it
beautiful and effortless.
beautiful, effortless, and intelligent.
</p>
</div>
</div>
@@ -67,10 +69,10 @@ export default function AboutPage() {
<div className="grid md:grid-cols-3 gap-8">
{[
{
icon: "🔓",
title: "Open Core",
icon: "🔒",
title: "Proprietary & Polished",
description:
"The core of DashCaddy is free and always will be. Premium features fund development, but the essentials are open to everyone.",
"DashCaddy is proprietary software built with care. The core platform is free to use, with Premium features for advanced orchestration.",
},
{
icon: "🏠",
@@ -79,10 +81,10 @@ export default function AboutPage() {
"DashCaddy runs entirely on your hardware. No cloud dependency, no telemetry, no phoning home. Your data never leaves your network.",
},
{
icon: "🛠️",
title: "Built to Last",
icon: "🤖",
title: "AI-Native",
description:
"We use proven technologies — Caddy, Docker, Node.js. No bleeding-edge frameworks that break every six months. Stable, reliable, boring (in the best way).",
"From the AI Intent Router to MCP server integration, DashCaddy is built for the AI era. Control your homelab with natural language or let AI assistants manage it for you.",
},
].map((value) => (
<div
@@ -108,26 +110,14 @@ export default function AboutPage() {
</h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
{[
{
name: "Caddy",
role: "Reverse Proxy & SSL",
icon: "🔒",
},
{
name: "Docker",
role: "Container Runtime",
icon: "🐳",
},
{
name: "Node.js",
role: "API Backend",
icon: "🟢",
},
{
name: "Technitium",
role: "DNS Server",
icon: "🌐",
},
{ name: "Caddy", role: "Reverse Proxy & SSL", icon: "🔒" },
{ name: "Docker", role: "Container Runtime", icon: "🐳" },
{ name: "Node.js", role: "API Backend", icon: "🟢" },
{ name: "Technitium", role: "DNS Server", icon: "🌐" },
{ name: "Express", role: "REST API + WebSocket", icon: "⚡" },
{ name: "MCP", role: "AI Protocol Layer", icon: "🤖" },
{ name: "Stripe", role: "Payment Processing", icon: "💳" },
{ name: "Jest", role: "Testing (1770+ tests)", icon: "✅" },
].map((tech) => (
<div
key={tech.name}
@@ -142,13 +132,36 @@ export default function AboutPage() {
</div>
</section>
{/* Stats */}
<section className="py-20 px-4 border-t border-surface-800">
<div className="max-w-4xl mx-auto">
<h2 className="text-2xl font-bold text-white mb-12 text-center">
By the Numbers
</h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-8">
{[
{ value: "92+", label: "App Templates" },
{ value: "1770+", label: "Test Cases" },
{ value: "260+", label: "API Endpoints" },
{ value: "5", label: "Languages" },
].map((stat) => (
<div key={stat.label} className="text-center">
<div className="text-4xl font-bold text-brand-400 mb-2">
{stat.value}
</div>
<p className="text-sm text-surface-400">{stat.label}</p>
</div>
))}
</div>
</div>
</section>
{/* Contact / Support */}
<section className="py-20 px-4 border-t border-surface-800">
<div className="max-w-3xl mx-auto text-center">
<h2 className="text-2xl font-bold text-white mb-6">Get In Touch</h2>
<p className="text-surface-300 mb-8">
Have questions, feedback, or want to contribute? We&apos;d love to
hear from you.
Have questions or feedback? We&apos;d love to hear from you.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<a
@@ -157,18 +170,12 @@ export default function AboutPage() {
>
Email Us
</a>
<a
href="#"
<Link
href="/docs"
className="px-6 py-3 rounded-lg border border-surface-700 hover:border-surface-500 text-surface-300 font-medium transition-colors"
>
Join Discord
</a>
<a
href="#"
className="px-6 py-3 rounded-lg border border-surface-700 hover:border-surface-500 text-surface-300 font-medium transition-colors"
>
GitHub
</a>
Read the Docs
</Link>
</div>
</div>
</section>
@@ -180,7 +187,7 @@ export default function AboutPage() {
Ready to simplify your homelab?
</h2>
<p className="text-surface-300 mb-8">
Start with the free tier. Upgrade when you&apos;re ready.
Install DashCaddy and start deploying services today. Free forever.
</p>
<Link
href="/pricing"
+5 -74
View File
@@ -1,74 +1,5 @@
import { NextRequest, NextResponse } from "next/server";
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: "2026-03-25.dahlia",
});
const PRICE_IDS: Record<string, string | undefined> = {
monthly: process.env.STRIPE_PRICE_MONTHLY,
yearly: process.env.STRIPE_PRICE_YEARLY,
};
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { plan, email } = body;
if (!plan || !PRICE_IDS[plan]) {
return NextResponse.json(
{ error: "Invalid plan. Choose 'monthly' or 'yearly'." },
{ status: 400 }
);
}
const priceId = PRICE_IDS[plan];
if (!priceId) {
return NextResponse.json(
{ error: "Price not configured. Please contact support." },
{ status: 500 }
);
}
const appUrl = process.env.NEXT_PUBLIC_APP_URL || "https://dashcaddy.net";
const sessionParams: Stripe.Checkout.SessionCreateParams = {
mode: "subscription",
payment_method_types: ["card"],
line_items: [
{
price: priceId,
quantity: 1,
},
],
success_url: `${appUrl}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${appUrl}/pricing`,
allow_promotion_codes: true,
billing_address_collection: "required",
subscription_data: {
trial_period_days: 14,
metadata: {
plan,
source: "dashcaddy-website",
},
},
metadata: {
plan,
},
};
// Pre-fill email if provided
if (email) {
sessionParams.customer_email = email;
}
const session = await stripe.checkout.sessions.create(sessionParams);
return NextResponse.json({ url: session.url });
} catch (error) {
console.error("Stripe checkout error:", error);
const message =
error instanceof Error ? error.message : "Internal server error";
return NextResponse.json({ error: message }, { status: 500 });
}
}
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
return NextResponse.json({ error: 'Checkout disabled for static export' }, { status: 503 });
}
+5
View File
@@ -0,0 +1,5 @@
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
return NextResponse.json({ error: 'Webhooks disabled for static export' }, { status: 503 });
}
+4 -99
View File
@@ -1,99 +1,4 @@
import { NextRequest, NextResponse } from "next/server";
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: "2026-03-25.dahlia",
});
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
export async function POST(request: NextRequest) {
const body = await request.text();
const signature = request.headers.get("stripe-signature");
if (!signature) {
return NextResponse.json(
{ error: "Missing stripe-signature header" },
{ status: 400 }
);
}
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
} catch (err) {
const message = err instanceof Error ? err.message : "Unknown error";
console.error(`Webhook signature verification failed: ${message}`);
return NextResponse.json({ error: message }, { status: 400 });
}
try {
switch (event.type) {
case "checkout.session.completed": {
const session = event.data.object as Stripe.Checkout.Session;
console.log("Checkout completed:", {
sessionId: session.id,
customerEmail: session.customer_email,
plan: session.metadata?.plan,
subscriptionId: session.subscription,
});
// TODO: Generate and deliver license key to customer
// This is where you'd:
// 1. Generate a DC-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX license code
// 2. Store it in your database
// 3. Email it to the customer
// 4. Associate it with the Stripe subscription ID
break;
}
case "customer.subscription.updated": {
const subscription = event.data.object as Stripe.Subscription;
console.log("Subscription updated:", {
subscriptionId: subscription.id,
status: subscription.status,
});
// TODO: Update license expiration based on subscription status
break;
}
case "customer.subscription.deleted": {
const subscription = event.data.object as Stripe.Subscription;
console.log("Subscription cancelled:", {
subscriptionId: subscription.id,
status: subscription.status,
});
// TODO: Deactivate/expire the license key
// The DashCaddy instance will gracefully downgrade to free tier
break;
}
case "invoice.payment_failed": {
const invoice = event.data.object as Stripe.Invoice;
console.log("Payment failed:", {
invoiceId: invoice.id,
customerEmail: invoice.customer_email,
});
// TODO: Notify customer about failed payment
// Consider a grace period before deactivating license
break;
}
default:
console.log(`Unhandled event type: ${event.type}`);
}
return NextResponse.json({ received: true });
} catch (error) {
console.error("Webhook handler error:", error);
return NextResponse.json(
{ error: "Webhook handler failed" },
{ status: 500 }
);
}
}
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
return NextResponse.json({ error: 'disabled' }, { status: 503 });
}
+344
View File
@@ -0,0 +1,344 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export default function DocsApiPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="API and Automation"
intro="DashCaddy is more than a dashboard — it exposes a real API and automation surface so you can drive deployments, DNS, proxy, certificates, monitoring, and operations programmatically or through AI."
>
<p>
Every action available in the DashCaddy UI is also available through a programmatic surface: a versioned REST
API, a JavaScript automation layer, an AI Intent Router for natural-language commands, an MCP Server for AI assistant
integration, a WebSocket channel for real-time events, a Prometheus endpoint for metrics, and a plugin system
for extending the platform. This guide covers each surface with concrete examples.
</p>
<p>
Whether you are wiring DashCaddy into a CI/CD pipeline, building a custom dashboard, or letting an AI assistant
manage your infrastructure, the automation layer is designed to be the primary interface the web UI is just
one consumer of it.
</p>
<h2>REST API</h2>
<p>
All platform operations are available under <code>/api/v1/</code>. The API covers service management, app
deployment, DNS automation, Caddy reverse-proxy integration, certificate workflows, health and status
reporting, user and admin operations, backup/restore, and more. The repository ships with an OpenAPI definition
so the public contract can mature into a full reference.
</p>
<p>
Requests and responses are JSON. The base URL is your DashCaddy host for example
<code> https://dashcaddy-host/api/v1/services</code>. All endpoints require authentication (see below) and
return structured error codes rather than opaque messages.
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# List all services
curl -H "Authorization: Bearer ***" \\
https://dashcaddy-host/api/v1/services
# Deploy from a template
curl -X POST -H "Authorization: Bearer ***" \\
-H "Content-Type: application/json" \\
-d '{"template":"jellyfin","name":"media","hostname":"media.lab"}' \\
https://dashcaddy-host/api/v1/services
# Restart a service
curl -X POST -H "Authorization: Bearer ***" \\
https://dashcaddy-host/api/v1/services/media/restart`}</code></pre>
<h2>Authentication</h2>
<p>
DashCaddy supports two authentication methods, chosen by how you access the API:
</p>
<h3>Session cookie (browser)</h3>
<p>
The web dashboard authenticates with a session cookie set after login (email magic link or username/password
with optional TOTP 2FA). API calls made from the browser carry the cookie automatically. This is the right
method for in-dashboard automation and userscripts.
</p>
<h3>API key (Bearer token)</h3>
<p>
For server-to-server automation, scripts, and the SDK, use an API key. Generate keys from
<strong> Settings API Keys</strong>. Keys are bearer tokens pass them in the <code>Authorization</code>
header on every request:
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`Authorization: Bearer dc_live_xxxxxxxxxxxxxxxxxxxx`}</code></pre>
<blockquote className="border-l-4 border-brand-500/50 bg-brand-500/5 p-4 rounded-r-lg">
<p className="text-surface-300">
<strong className="text-brand-400">Security:</strong> API keys grant the same permissions as the user who
created them, scoped by RBAC role. Store keys in a secret manager never commit them to source control.
Rotate keys immediately if one is leaked.
</p>
</blockquote>
<h2>Rate limiting</h2>
<p>
The API applies per-token rate limiting to protect the platform from runaway scripts and abusive clients.
Limits are generous for normal operation: interactive dashboard usage will never hit them. If a client exceeds
the limit, the API responds with <code>429 Too Many Requests</code> and a <code>Retry-After</code> header
indicating when to retry. Back off and retry do not hammer the endpoint.
</p>
<p>
For high-volume automation (e.g. polling service status in a tight loop), prefer the <strong>WebSocket
channel</strong> or the <strong>Prometheus endpoint</strong> over repeated REST polling. Both are designed for
frequent reads and do not count against the REST rate limit.
</p>
<h2>JavaScript automation</h2>
<p>
For programmatic automation, use the REST API directly with <code>fetch</code> or any HTTP client. The API is
JSON-based, uses Bearer token authentication, and returns structured error codes. Here is a minimal helper
you can drop into any Node.js, Bun, or browser project:
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{['class DashCaddy {', '',
' constructor(opts) {', '',
' this.baseUrl = opts.baseUrl;', '',
' this.token = opts.token;', '',
' }', '',
'', '',
' async request(path, options) {', '',
' options = options || {};', '',
' var url = this.baseUrl + "/api/v1" + path;', '',
' var res = await fetch(url, {', '',
' method: options.method || "GET",', '',
' body: options.body,', '',
' headers: {', '',
' "Content-Type": "application/json",', '',
' "Authorization": "Bearer " + this.token', '',
' }', '',
' });', '',
' var body = await res.json();', '',
' if (!res.ok) throw { code: body.error, status: res.status };', '',
' return body;', '',
' }', '',
'', '',
' // List services', '',
' services() { return this.request("/services"); }', '',
'', '',
' // Deploy from template', '',
' deploy(template, name, hostname) {', '',
' return this.request("/services", {', '',
' method: "POST",', '',
' body: JSON.stringify({ template, name, hostname })', '',
' });', '',
' }', '',
'', '',
' // Restart a service', '',
' restart(id) {', '',
' return this.request("/services/" + id + "/restart", { method: "POST" });', '',
' }', '',
'}'].join('\n')}</code></pre>
<p>
Every request returns a structured JSON response or throws an error object carrying the error
code, HTTP status, and message so your automation can branch on specific failure conditions.
</p>
<h2>AI Intent Router</h2>
<p>
The <strong>AI Intent Router</strong> accepts natural-language commands and translates them into real
infrastructure actions through the same API. This turns ad-hoc operator requests (&ldquo;restart the media
server&rdquo;, &ldquo;is postgres up?&rdquo;, &ldquo;deploy redis&rdquo;) into reproducible, logged operations
no need to remember endpoint paths or parameter names.
</p>
<p>
The router parses intent, maps it to the correct API call, executes it, and returns both a human-readable
summary and the raw API result. Every intent execution is recorded in the audit log just like a manual action.
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Natural-language operation
POST /api/v1/ai/intent
{
"message": "Restart the media server and check its health"
}
# Response
{
"summary": "Restarted 'media' and confirmed health: healthy",
"actions": [
{ "method": "POST", "path": "/api/v1/services/media/restart", "status": 200 },
{ "method": "GET", "path": "/api/v1/services/media/health", "status": 200 }
]
}`}</code></pre>
<p>
Example intents: &ldquo;deploy the postgres template as <code>db</code> on <code>db.lab</code>&rdquo;,
&ldquo;list all unhealthy services&rdquo;, &ldquo;rotate the TLS cert for <code>wiki.lab</code>&rdquo;,
&ldquo;create a DNS record for <code>api.lab</code> pointing at 10.0.0.5&rdquo;.
</p>
<h2>MCP Server</h2>
<p>
The built-in <strong>MCP (Model Context Protocol) Server</strong> exposes DashCaddy operations as tools that
AI assistants and external automation can call directly. Connect your assistant to the MCP endpoint and it can
list services, deploy templates, manage DNS, inspect health, and trigger operations all through the standard
MCP tool interface, with full audit logging.
</p>
<p>
To connect Claude Desktop, GPT, or another MCP-compatible assistant, add the DashCaddy MCP server to your
client&apos;s MCP configuration:
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`{
"mcpServers": {
"dashcaddy": {
"url": "https://dashcaddy-host/mcp",
"headers": {
"Authorization": "Bearer dc_live_xxxxxxxxxxxxxxxxxxxx"
}
}
}
}`}</code></pre>
<p>
Once connected, the assistant discovers DashCaddy&apos;s tools automatically and can invoke them in response to
your requests &ldquo;ask DashCaddy which services are down&rdquo;, &ldquo;have DashCaddy deploy Grafana&rdquo;,
etc. This is the most natural way to operate infrastructure through conversation.
</p>
<h2>WebSocket real-time events</h2>
<p>
The dashboard subscribes to a <strong>WebSocket channel</strong> for live updates: service health changes,
container starts and stops, deployment progress, DNS changes, and fleet events arrive in real time without
polling. You can consume the same channel in your own dashboards, chatops bots, or automation.
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`const ws = new WebSocket('wss://dashcaddy-host/api/v1/events', {
headers: { Authorization: 'Bearer ' + process.env.DC_TOKEN },
});
ws.on('message', (data) => {
const event = JSON.parse(data);
console.log(event.type, event.payload);
});`}</code></pre>
<p>
Common event types you will see on the channel:
</p>
<table>
<thead>
<tr>
<th>Event type</th>
<th>Emitted when</th>
</tr>
</thead>
<tbody>
<tr><td><code>service.health</code></td><td>A service transitions between healthy / unhealthy / down</td></tr>
<tr><td><code>service.started</code></td><td>A container starts successfully</td></tr>
<tr><td><code>service.stopped</code></td><td>A container stops (graceful or crash)</td></tr>
<tr><td><code>deploy.progress</code></td><td>A template deployment advances through its stages</td></tr>
<tr><td><code>deploy.complete</code></td><td>A deployment finishes (success or failure)</td></tr>
<tr><td><code>dns.changed</code></td><td>A DNS record is created, updated, or removed</td></tr>
<tr><td><code>proxy.updated</code></td><td>A Caddy route is applied or removed</td></tr>
<tr><td><code>cert.issued</code></td><td>A TLS certificate is issued or renewed</td></tr>
<tr><td><code>fleet.host</code></td><td>A fleet host changes state (Premium)</td></tr>
<tr><td><code>audit.event</code></td><td>A user or API action is logged for audit</td></tr>
</tbody>
</table>
<h2>Prometheus metrics endpoint</h2>
<p>
DashCaddy exposes a Prometheus-format metrics endpoint at <code>/metrics</code> for service health, container
status, request counts, certificate expiry, and system indicators. Scrape it with Prometheus and visualize in
Grafana. See <a href="/docs/integrations">Integrations</a> for a full scrape config.
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Scrape config (prometheus.yml)
scrape_configs:
- job_name: 'dashcaddy'
metrics_path: /metrics
static_configs:
- targets: ['dashcaddy-host:3000']
# Sample exported metrics
dashcaddy_service_health{service="media"} 1
dashcaddy_container_running{container="db"} 1
dashcaddy_http_requests_total{service="wiki",code="200"} 48213
dashcaddy_cert_expiry_days{domain="media.lab"} 87`}</code></pre>
<h2>Health and readiness probes</h2>
<p>
Two lightweight probes let orchestrators and load balancers check DashCaddy itself:
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Liveness — is the process up?
GET /healthz
# Readiness — can it serve (Docker, Caddy, DNS connected)?
GET /readyz`}</code></pre>
<p>
Use <code>/healthz</code> for container restart policies and <code>/readyz</code> for traffic gating. If
<code> /readyz</code> fails but <code>/healthz</code> passes, a dependency (Docker socket, Caddy Admin API, or
Technitium DNS) is unreachable see <a href="/docs/troubleshooting">Troubleshooting</a>.
</p>
<h2>Plugin &amp; extension system</h2>
<p>
DashCaddy includes a <strong>plugin/extension system</strong> with hooks into the deployment, DNS, proxy, and
monitoring pipelines. Write extensions to react to service lifecycle events, inject custom Caddy directives,
emit additional metrics, or integrate third-party tools without forking the core.
</p>
<p>
Plugins register for lifecycle hooks (e.g. <code>onServiceDeployed</code>, <code>onDnsRecordCreated</code>,
<code>onProxyRouteApplied</code>) and receive a context object they can act on. A plugin can modify the
generated Caddyfile before it is applied, push a notification when a service goes unhealthy, or export custom
metrics alongside the built-in ones. Plugins are loaded at startup and run in the same process.
</p>
<h2>Structured error codes</h2>
<p>
The API returns <strong>80 structured error codes</strong> across <strong>12 modules</strong> rather
than opaque messages, so your automation can branch on specific failure conditions DNS token invalid, Caddy
unreachable, license expired, rate limited instead of parsing strings. Every error response includes the
machine-readable code, the HTTP status, and a human-readable message.
</p>
<table>
<thead>
<tr>
<th>Module</th>
<th>Example error codes</th>
</tr>
</thead>
<tbody>
<tr><td>auth</td><td><code>AUTH_INVALID_TOKEN</code>, <code>AUTH_PERMISSION_DENIED</code>, <code>AUTH_2FA_REQUIRED</code></td></tr>
<tr><td>service</td><td><code>SERVICE_NOT_FOUND</code>, <code>SERVICE_ALREADY_EXISTS</code>, <code>SERVICE_UNHEALTHY</code></td></tr>
<tr><td>deploy</td><td><code>DEPLOY_TEMPLATE_INVALID</code>, <code>DEPLOY_PORT_CONFLICT</code>, <code>DEPLOY_FAILED</code></td></tr>
<tr><td>dns</td><td><code>DNS_TOKEN_INVALID</code>, <code>DNS_ZONE_NOT_FOUND</code>, <code>DNS_RECORD_EXISTS</code></td></tr>
<tr><td>proxy</td><td><code>PROXY_CADDY_UNREACHABLE</code>, <code>PROXY_CONFIG_INVALID</code>, <code>PROXY_UPSTREAM_TIMEOUT</code></td></tr>
<tr><td>cert</td><td><code>CERT_ISSUANCE_FAILED</code>, <code>CERT_EXPIRED</code>, <code>CERT_NOT_TRUSTED</code></td></tr>
<tr><td>license</td><td><code>LICENSE_EXPIRED</code>, <code>LICENSE_INVALID</code>, <code>LICENSE_MACHINE_LIMIT</code></td></tr>
<tr><td>user</td><td><code>USER_NOT_FOUND</code>, <code>USER_ALREADY_EXISTS</code>, <code>USER_INVITE_EXPIRED</code></td></tr>
<tr><td>backup</td><td><code>BACKUP_FAILED</code>, <code>BACKUP_CORRUPT</code>, <code>RESTORE_CONFLICT</code></td></tr>
<tr><td>recipe</td><td><code>RECIPE_INVALID</code>, <code>RECIPE_COMPONENT_FAILED</code> (Premium)</td></tr>
<tr><td>swarm</td><td><code>SWARM_NOT_INITIALIZED</code>, <code>SWARM_NODE_UNREACHABLE</code> (Premium)</td></tr>
<tr><td>fleet</td><td><code>FLEET_HOST_OFFLINE</code>, <code>FLEET_DEPLOY_PLAN_FAILED</code> (Premium)</td></tr>
</tbody>
</table>
<p>
Handle errors by code in your automation:
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`try {
await dc.services.deploy({ template: 'postgres', name: 'db', hostname: 'db.lab' });
} catch (err) {
if (err.code === 'DEPLOY_PORT_CONFLICT') {
// pick a different port and retry
} else if (err.code === 'LICENSE_EXPIRED') {
// alert ops to renew
} else {
throw err; // unknown — surface to the operator
}
}`}</code></pre>
<h2>Why automation matters</h2>
<p>
DashCaddy can execute the full infrastructure chain around a service, not just report its state after the fact.
Between the REST API, the AI Intent Router, MCP, WebSockets, Prometheus, and the plugin system, you
have every surface you need to make DashCaddy a first-class citizen of your automation stack. Start with a
simple <code>curl</code> call, and add AI and event-driven flows as your needs grow.
</p>
<p>
For the infrastructure that backs all of this, see <a href="/docs/integrations">Integrations</a>. When things go
wrong, the <a href="/docs/troubleshooting">Troubleshooting</a> guide walks each layer with commands and fixes.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+381
View File
@@ -0,0 +1,381 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export default function DocsFirstServicePage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Deploy Your First Service"
intro="This is where DashCaddy becomes real: take an app from a template, an existing container, or a raw target port to a DNS-backed, reverse-proxied, HTTPS-enabled service visible in one dashboard — in a few clicks."
>
<h2>Prerequisites</h2>
<blockquote className="border-l-4 border-brand-500/50 bg-brand-500/5 p-4 rounded-r-lg">
<p className="text-surface-300">
<strong className="text-brand-400">Before you begin:</strong> Complete the <a href="/docs/installation" className="text-brand-400 hover:text-brand-300 underline">Installation Guide</a> first. You need a running DashCaddy instance with the dashboard accessible, the API responding on <code>/healthz</code>, and Caddy's Admin API reachable. Technitium DNS is recommended but optional — services will still deploy without it using direct IP access.
</p>
</blockquote>
<h2>What DashCaddy handles for you</h2>
<p>When you deploy a service, DashCaddy automates the full infrastructure chain:</p>
<ul>
<li>Container deployment (from template) or adoption of an existing container</li>
<li>Service record creation in the DashCaddy state store</li>
<li>DNS record creation through Technitium DNS (when configured)</li>
<li>Caddy reverse-proxy route configuration via the Admin API</li>
<li>Automatic TLS certificate issuance through Caddy's internal CA / DashCA</li>
<li>Real-time health tracking and WebSocket status updates on the dashboard</li>
</ul>
<p>
You provide the intent (which app, which hostname), and DashCaddy coordinates every layer atomically.
If any step fails, the operation rolls back cleanly you never end up with a half-wired service.
</p>
<h2>Three ways to add a service</h2>
<h3>1. Pick from 92+ one-click templates</h3>
<p>
The template library covers the most popular self-hosted applications media servers, dashboards,
databases, note apps, automation tools, and more. Each template bundles sane defaults for ports,
volumes, environment variables, and the recommended subdomain.
</p>
<h3>2. Use Service Discovery to auto-detect existing containers</h3>
<p>
Already running Docker containers? DashCaddy's <strong>Service Discovery</strong> scans the host and lists
every running container, marking any that are not yet managed.
</p>
<h3>3. Define a service manually</h3>
<p>
For custom images or apps not in the template library, define the service by hand with full control
over image, ports, volumes, and environment variables.
</p>
<h2>Step-by-step: Deploy Plex</h2>
<p>
Let's walk through deploying Plex Media Server using the template library. This is the most common
path for new users and demonstrates the full deployment chain.
</p>
<h3>Step 1: Open the template library</h3>
<p>
From the dashboard sidebar, click <strong>New Service From Template</strong>. The template library
opens with a searchable grid of 92+ applications.
</p>
<h3>Step 2: Find Plex</h3>
<p>
Type "Plex" in the search bar, or browse the "Media" category. Click the Plex template card to
open its configuration form.
</p>
<h3>Step 3: Configure the service</h3>
<p>The form is pre-filled with sensible defaults. Review and adjust:</p>
<ul>
<li><strong>Service name:</strong> <code>plex</code> (used for internal identification)</li>
<li><strong>Hostname:</strong> <code>plex.local</code> (the subdomain DashCaddy will publish)</li>
<li><strong>Container image:</strong> <code>linuxserver/plex:latest</code></li>
<li><strong>Port:</strong> <code>32400</code> (Plex's default web interface port)</li>
<li><strong>Volumes:</strong> <code>/opt/plex/config:/config</code> and <code>/mnt/media:/media</code></li>
<li><strong>Environment variables:</strong> <code>PUID=1000</code>, <code>PGID=1000</code>, <code>VERSION=docker</code></li>
</ul>
<h3>Step 4: Deploy</h3>
<p>
Click <strong>Deploy</strong>. DashCaddy now executes the full deployment chain:
</p>
<ol>
<li>Pulls the <code>linuxserver/plex:latest</code> Docker image</li>
<li>Creates and starts the container with your configured volumes and environment</li>
<li>Creates a service record in the DashCaddy state store</li>
<li>Generates a Caddy route mapping <code>plex.local</code> → <code>localhost:32400</code></li>
<li>Applies the route through the Caddy Admin API</li>
<li>Requests a TLS certificate for <code>plex.local</code> via DashCA</li>
<li>Creates an A record in Technitium DNS pointing <code>plex.local</code> to your host IP</li>
<li>Starts health checks and reports status on the dashboard</li>
</ol>
<h3>Step 5: Verify</h3>
<p>
Within 30 seconds, the service card on the dashboard should show <strong>Healthy</strong> with a green
status indicator. Click the service to see its detail page, which shows:
</p>
<ul>
<li>Container logs (live-streamed via WebSocket)</li>
<li>Resource usage (CPU, memory, network I/O)</li>
<li>Caddy route configuration</li>
<li>DNS record details</li>
<li>Certificate expiration date</li>
<li>Health check history</li>
</ul>
<h2>Service Discovery: Adopt existing containers</h2>
<p>
If you already have Docker containers running that you want DashCaddy to manage, use Service Discovery
instead of redeploying from scratch.
</p>
<h3>How it works</h3>
<p>
Service Discovery scans the Docker socket and lists every running container on the host. Containers
that are already managed by DashCaddy are marked as "Managed." Unmanaged containers are listed with
their image name, exposed ports, and current status.
</p>
<h3>Adopt a container</h3>
<ol>
<li>Open <strong>Service Discovery</strong> from the sidebar</li>
<li>Review the list of detected containers</li>
<li>For each one you want to manage, click <strong>Adopt</strong></li>
<li>Provide a hostname/subdomain (e.g., <code>grafana.local</code>)</li>
<li>Specify which port to expose (if the container exposes multiple)</li>
<li>Click <strong>Adopt &amp; Configure</strong></li>
</ol>
<p>
DashCaddy creates a service record, generates the Caddy route, DNS record, and certificate — without
restarting or modifying the running container. The container continues running with its existing
configuration; DashCaddy simply adds the proxy and DNS layers on top.
</p>
<h2>Behind the scenes: The deployment chain</h2>
<p>
Understanding what happens during a deployment helps you troubleshoot when things go wrong. Here's
the full chain, layer by layer:
</p>
<h3>1. Docker: Container creation</h3>
<p>
The orchestration layer calls the Docker API to create a container from the specified image. It attaches
the container to the <code>dashcaddy-net</code> bridge network, mounts the configured volumes, and injects
environment variables. The container starts in the background and begins listening on its configured port.
</p>
<h3>2. DashCaddy: Service record</h3>
<p>
A service record is written to the DashCaddy state store (a SQLite database in <code>./data/services.db</code>).
The record includes the service name, hostname, backend port, container ID, deployment timestamp, and
configuration metadata. This record is the source of truth for the dashboard and API.
</p>
<h3>3. Caddy: Reverse proxy route</h3>
<p>
The Caddyfile-as-Code builder generates a route configuration:
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`plex.local {
reverse_proxy localhost:32400
encode gzip
header / {
Strict-Transport-Security "max-age=31536000;"
}
}`}</code></pre>
<p>
This configuration is applied atomically through the Caddy Admin API (<code>POST /load</code>). Caddy
reloads its configuration without downtime and begins routing traffic for <code>plex.local</code> to
<code>localhost:32400</code>.
</p>
<h3>4. DNS: Record creation</h3>
<p>
If Technitium DNS is configured, the orchestration layer calls the Technitium API to create an A record:
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`POST /api/zones/records/create
{
"zone": "local",
"type": "A",
"name": "plex",
"ipAddress": "192.168.1.100"
}`}</code></pre>
<p>
The DNS record propagates immediately (Technitium is authoritative for the <code>local</code> zone).
Clients on your network can now resolve <code>plex.local</code> to your host's IP address.
</p>
<h3>5. TLS: Certificate issuance</h3>
<p>
Caddy's built-in ACME client detects the new hostname and requests a certificate. For internal domains
like <code>plex.local</code>, Caddy uses its internal CA (DashCA) rather than Let's Encrypt. The certificate
is issued, stored in Caddy's data directory, and served automatically for all HTTPS connections to
<code>plex.local</code>.
</p>
<p>
The certificate is valid for 90 days and renewed automatically 30 days before expiration. DashCaddy
tracks certificate expiration dates and surfaces warnings on the dashboard when renewal is approaching.
</p>
<h3>6. Health: Continuous monitoring</h3>
<p>
Once the service is deployed, DashCaddy starts a health check loop that runs every 30 seconds. The health
check performs an HTTP GET to the backend port and expects a 2xx or 3xx response. If the check fails three
times in a row, the service is marked <strong>Unhealthy</strong> on the dashboard and an event is logged.
</p>
<p>
Health status updates are pushed to the dashboard over WebSocket, so you see status changes in real-time
without refreshing the page.
</p>
<h2>Configuration reference</h2>
<p>
When deploying a service, these are the configuration fields available in the deployment form:
</p>
<div className="overflow-x-auto">
<table className="w-full text-left text-sm">
<thead>
<tr className="border-b border-surface-700">
<th className="py-3 pr-4 font-semibold text-surface-200">Field</th>
<th className="py-3 pr-4 font-semibold text-surface-200">Required</th>
<th className="py-3 font-semibold text-surface-200">Description</th>
</tr>
</thead>
<tbody className="text-surface-300">
<tr className="border-b border-surface-800">
<td className="py-3 pr-4 font-mono text-xs">Service Name</td>
<td className="py-3 pr-4">Yes</td>
<td className="py-3">Internal identifier (lowercase, no spaces)</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4 font-mono text-xs">Hostname</td>
<td className="py-3 pr-4">Yes</td>
<td className="py-3">Subdomain for the service (e.g., plex.local)</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4 font-mono text-xs">Container Image</td>
<td className="py-3 pr-4">Yes</td>
<td className="py-3">Docker image (e.g., linuxserver/plex:latest)</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4 font-mono text-xs">Backend Port</td>
<td className="py-3 pr-4">Yes</td>
<td className="py-3">Port the container listens on internally</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4 font-mono text-xs">Volumes</td>
<td className="py-3 pr-4">No</td>
<td className="py-3">Host:container path mappings for persistent storage</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4 font-mono text-xs">Environment Variables</td>
<td className="py-3 pr-4">No</td>
<td className="py-3">Key-value pairs injected into the container</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4 font-mono text-xs">Network</td>
<td className="py-3 pr-4">No</td>
<td className="py-3">Docker network to attach (default: dashcaddy-net)</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4 font-mono text-xs">Restart Policy</td>
<td className="py-3 pr-4">No</td>
<td className="py-3">Container restart behavior (default: unless-stopped)</td>
</tr>
<tr>
<td className="py-3 pr-4 font-mono text-xs">Health Check Path</td>
<td className="py-3 pr-4">No</td>
<td className="py-3">HTTP path for health checks (default: /)</td>
</tr>
</tbody>
</table>
</div>
<h2>Common scenarios</h2>
<h3>Deploy an internal-only service</h3>
<p>
For services that should stay on the local network (not exposed to the internet), use a <code>.local</code>
or <code>.internal</code> TLD. Ensure client devices trust the DashCA root certificate (download it from
the DashCA page in the dashboard). The service will be accessible at <code>https://servicename.local</code>
with a trusted HTTPS connection, but only from devices on your network that have the root cert installed.
</p>
<h3>Deploy a service with custom environment variables</h3>
<p>
When deploying manually or editing a template, you can add custom environment variables in the deployment
form. Each variable is a key-value pair that gets injected into the container at startup. Common examples:
</p>
<ul>
<li><code>PUID=1000</code> / <code>PGID=1000</code> user/group ID for file permissions (LinuxServer images)</li>
<li><code>TZ=America/New_York</code> timezone for log timestamps</li>
<li><code>DB_PASSWORD=secret</code> database credentials for apps like Nextcloud</li>
</ul>
<h3>Deploy multiple services on the same host</h3>
<p>
DashCaddy handles multiple services on the same host automatically. Each service gets a unique subdomain,
and Caddy routes traffic based on the <code>Host</code> header. You can run Plex on <code>plex.local</code>,
Nextcloud on <code>nextcloud.local</code>, and Grafana on <code>grafana.local</code> all on the same
host, all on port 443, with no port conflicts.
</p>
<h3>Adopt a service that's already running</h3>
<p>
If you have a container running outside of DashCaddy (e.g., started manually with <code>docker run</code>),
use Service Discovery to adopt it. DashCaddy will add the proxy and DNS layers without restarting the
container. The container's existing volumes, environment, and network configuration are preserved.
</p>
<h2>Verification checklist</h2>
<p>After deploying a service, verify each layer:</p>
<ol>
<li>
<strong>Container status:</strong> The service card on the dashboard shows <strong>Running</strong> with
a green status indicator
</li>
<li>
<strong>Backend port:</strong> <code>curl http://localhost:32400</code> returns a response from the
application
</li>
<li>
<strong>Caddy route:</strong> <code>curl http://localhost:2019/config/</code> shows a route for your
hostname
</li>
<li>
<strong>DNS resolution:</strong> <code>ping plex.local</code> resolves to your host's IP address
</li>
<li>
<strong>TLS certificate:</strong> <code>curl -v https://plex.local</code> shows a valid certificate
(no warnings if DashCA root is installed)
</li>
<li>
<strong>Health check:</strong> The dashboard shows <strong>Healthy</strong> and the health check
history graph shows consistent success
</li>
</ol>
<h2>Troubleshooting</h2>
<p>If the service does not come up correctly, debug in order, layer by layer:</p>
<ol>
<li><strong>Container:</strong> Check <code>docker logs &lt;container_name&gt;</code> for startup errors</li>
<li><strong>Backend port:</strong> Verify the container is listening on the expected port</li>
<li><strong>Caddy route:</strong> Confirm the route exists in the Caddy Admin API</li>
<li><strong>DNS resolution:</strong> Check that the DNS record was created in Technitium</li>
<li><strong>TLS trust:</strong> Verify the DashCA root certificate is installed on the client device</li>
<li><strong>Dashboard state:</strong> Check the service detail page for error messages or failed health checks</li>
</ol>
<p>
See the <a href="/docs/troubleshooting" className="text-brand-400 hover:text-brand-300 underline">Troubleshooting Guide</a> for
the full checklist and common error patterns.
</p>
<h2>Next steps</h2>
<ul>
<li>
<a href="/docs/overview" className="text-brand-400 hover:text-brand-300 underline">
Product Overview
</a> understand the full architecture and design philosophy
</li>
<li>
Explore the <strong>Template Library</strong> to discover other applications you can deploy
</li>
<li>
Configure <strong>Service Discovery</strong> to adopt existing containers
</li>
<li>
Set up <strong>Prometheus metrics</strong> for external monitoring integration
</li>
</ul>
</DocsLayout>
<Footer />
</div>
);
}
+385
View File
@@ -0,0 +1,385 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export default function DocsInstallationPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Installation Guide"
intro="DashCaddy ships as a Docker-based deployment with a guided installer that handles dependency checks, configuration generation, and first launch. This guide covers prerequisites, the installer flow, and what to expect on first run."
>
<h2>System requirements</h2>
<p>Before installing DashCaddy, verify that your host meets these minimum requirements:</p>
<div className="overflow-x-auto">
<table className="w-full text-left text-sm">
<thead>
<tr className="border-b border-surface-700">
<th className="py-3 pr-4 font-semibold text-surface-200">Component</th>
<th className="py-3 pr-4 font-semibold text-surface-200">Minimum</th>
<th className="py-3 font-semibold text-surface-200">Recommended</th>
</tr>
</thead>
<tbody className="text-surface-300">
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">Operating System</td>
<td className="py-3 pr-4">Linux (Ubuntu 20.04+, Debian 11+, CentOS 8+)</td>
<td className="py-3">Ubuntu 22.04 LTS or Debian 12</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">CPU</td>
<td className="py-3 pr-4">2 cores</td>
<td className="py-3">4+ cores</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">RAM</td>
<td className="py-3 pr-4">2 GB</td>
<td className="py-3">4+ GB (8 GB for 20+ services)</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">Disk</td>
<td className="py-3 pr-4">10 GB free</td>
<td className="py-3">50+ GB SSD</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">Docker</td>
<td className="py-3 pr-4">20.10+</td>
<td className="py-3">Latest stable</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">Docker Compose</td>
<td className="py-3 pr-4">v2.0+</td>
<td className="py-3">Latest stable</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">Node.js</td>
<td className="py-3 pr-4">20.x LTS</td>
<td className="py-3">20.x LTS or 22.x LTS</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">Caddy</td>
<td className="py-3 pr-4">2.6+ with Admin API</td>
<td className="py-3">Latest stable</td>
</tr>
<tr>
<td className="py-3 pr-4">Technitium DNS</td>
<td className="py-3 pr-4">Optional</td>
<td className="py-3">Latest stable (for auto DNS)</td>
</tr>
</tbody>
</table>
</div>
<h2>Quick start with start.sh</h2>
<p>
The fastest path to a running DashCaddy is the bundled <code>start.sh</code> script. It performs environment
checks, pulls the required containers, generates configuration, and brings the stack up in a single command.
</p>
<h3>Step 1: Clone the repository</h3>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`curl -fsSL https://get.dashcaddy.net | bash
cd dashcaddy`}</code></pre>
<h3>Step 2: Make the launcher executable</h3>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`chmod +x start.sh`}</code></pre>
<h3>Step 3: Run the installer</h3>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`./start.sh`}</code></pre>
<p>
The script is idempotent re-running it will reconcile the stack rather than clobber an existing install.
If DashCaddy is already running, <code>start.sh</code> detects this and offers to update configuration or
restart services instead of reinstalling from scratch.
</p>
<h3>What start.sh does</h3>
<p>Under the hood, the script performs these steps:</p>
<ol>
<li><strong>Dependency validation</strong> checks for Docker, Docker Compose, Node.js, and Caddy</li>
<li><strong>Port availability check</strong> verifies ports 80, 443, and 2019 (Caddy Admin API) are free</li>
<li><strong>Configuration generation</strong> creates <code>.env</code> file with sensible defaults</li>
<li><strong>Docker image pull</strong> fetches the DashCaddy API and dashboard images</li>
<li><strong>Container startup</strong> launches the stack via Docker Compose</li>
<li><strong>Health check</strong> waits for the API to respond on <code>/healthz</code></li>
<li><strong>Dashboard URL display</strong> prints the access URL and initial admin credentials</li>
</ol>
<h2>Docker Compose configuration</h2>
<p>
If you prefer to inspect or customize the Docker Compose configuration before launching, here's the
default <code>docker-compose.yml</code> that <code>start.sh</code> generates:
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`version: '3.8'
services:
dashcaddy-api:
image: samiahmed7777/dashcaddy-api:latest
container_name: dashcaddy-api
restart: unless-stopped
ports:
- "3001:3001" # API port
volumes:
- ./data:/app/data
- ./config:/app/config
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
- NODE_ENV=production
- DASHCADDY_PORT=3001
- CADDY_ADMIN_URL=http://caddy:2019
- TECHNITIUM_API_URL=http://technitium:5380
- TECHNITIUM_API_TOKEN=\${TECHNITIUM_API_TOKEN}
- JWT_SECRET=\${JWT_SECRET}
- DASHCA_ENABLED=true
depends_on:
- caddy
- technitium
networks:
- dashcaddy-net
dashcaddy-dashboard:
image: samiahmed7777/dashcaddy-dashboard:latest
container_name: dashcaddy-dashboard
restart: unless-stopped
ports:
- "3000:3000" # Dashboard port
environment:
- NEXT_PUBLIC_API_URL=http://localhost:3001
depends_on:
- dashcaddy-api
networks:
- dashcaddy-net
caddy:
image: caddy:2-alpine
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "2019:2019" # Admin API
volumes:
- ./caddy/Caddyfile:/etc/caddy/Caddyfile
- ./caddy/data:/data
- ./caddy/config:/config
networks:
- dashcaddy-net
technitium:
image: technitium/dns-server:latest
container_name: technitium
restart: unless-stopped
ports:
- "5380:5380" # Web console
- "53:53/udp"
- "53:53/tcp"
volumes:
- ./technitium:/etc/dns
environment:
- DNS_SERVER_DOMAIN=local
networks:
- dashcaddy-net
networks:
dashcaddy-net:
driver: bridge`}</code></pre>
<h2>Environment variables reference</h2>
<p>
DashCaddy's behavior is controlled through environment variables in the <code>.env</code> file. Here's
a complete reference:
</p>
<div className="overflow-x-auto">
<table className="w-full text-left text-sm">
<thead>
<tr className="border-b border-surface-700">
<th className="py-3 pr-4 font-semibold text-surface-200">Variable</th>
<th className="py-3 pr-4 font-semibold text-surface-200">Default</th>
<th className="py-3 font-semibold text-surface-200">Description</th>
</tr>
</thead>
<tbody className="text-surface-300">
<tr className="border-b border-surface-800">
<td className="py-3 pr-4 font-mono text-xs">NODE_ENV</td>
<td className="py-3 pr-4">production</td>
<td className="py-3">Runtime environment (development or production)</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4 font-mono text-xs">DASHCADDY_PORT</td>
<td className="py-3 pr-4">3001</td>
<td className="py-3">Port for the DashCaddy API server</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4 font-mono text-xs">CADDY_ADMIN_URL</td>
<td className="py-3 pr-4">http://caddy:2019</td>
<td className="py-3">Caddy Admin API endpoint</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4 font-mono text-xs">TECHNITIUM_API_URL</td>
<td className="py-3 pr-4">http://technitium:5380</td>
<td className="py-3">Technitium DNS API endpoint</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4 font-mono text-xs">TECHNITIUM_API_TOKEN</td>
<td className="py-3 pr-4">(required)</td>
<td className="py-3">API token for Technitium DNS authentication</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4 font-mono text-xs">JWT_SECRET</td>
<td className="py-3 pr-4">(auto-generated)</td>
<td className="py-3">Secret key for JWT token signing</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4 font-mono text-xs">DASHCA_ENABLED</td>
<td className="py-3 pr-4">true</td>
<td className="py-3">Enable internal certificate authority</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4 font-mono text-xs">BASE_DOMAIN</td>
<td className="py-3 pr-4">local</td>
<td className="py-3">Base domain for service hostnames</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4 font-mono text-xs">PROMETHEUS_ENABLED</td>
<td className="py-3 pr-4">true</td>
<td className="py-3">Expose Prometheus metrics at /metrics</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4 font-mono text-xs">LOG_LEVEL</td>
<td className="py-3 pr-4">info</td>
<td className="py-3">Logging verbosity (debug, info, warn, error)</td>
</tr>
<tr>
<td className="py-3 pr-4 font-mono text-xs">DATA_DIR</td>
<td className="py-3 pr-4">./data</td>
<td className="py-3">Path for persistent state storage</td>
</tr>
</tbody>
</table>
</div>
<h2>The Smart Defaults Wizard</h2>
<blockquote className="border-l-4 border-brand-500/50 bg-brand-500/5 p-4 rounded-r-lg">
<p className="text-surface-300">
<strong className="text-brand-400">Smart Defaults Wizard:</strong> On first launch, the dashboard opens to the Smart Defaults Wizard. It surveys your host and pre-fills sensible choices so you can go from install to a working deployment in minutes. Every default is editable — the wizard simply gives you a known-good starting point instead of a blank slate.
</p>
</blockquote>
<p>The wizard performs these tasks automatically:</p>
<ul>
<li><strong>Caddy detection</strong> — detects the Caddy Admin API endpoint and validates connectivity</li>
<li><strong>Technitium detection</strong> — detects Technitium DNS (if installed) and configures the API token and zone</li>
<li><strong>Domain suggestion</strong> — proposes a base domain and naming convention for new services</li>
<li><strong>DashCA initialization</strong> — initializes the internal certificate authority and offers the root certificate for download</li>
<li><strong>Admin account creation</strong> — creates the initial admin account and offers TOTP 2FA enrollment</li>
<li><strong>Network scanning</strong> — scans for existing Docker containers and offers to adopt them via Service Discovery</li>
</ul>
<h2>Manual setup</h2>
<p>
If you want direct control over paths, services, Caddy, and DNS integration, you can deploy manually
instead of using <code>start.sh</code>:
</p>
<ol>
<li>Run the installer: <code>curl -fsSL https://get.dashcaddy.net | bash</code></li>
<li>Install the API dependencies: <code>cd dashcaddy && npm ci</code></li>
<li>Prepare Caddy and confirm the Admin API is reachable on port 2019</li>
<li>Prepare Technitium DNS if you want automatic DNS changes (optional)</li>
<li>Copy <code>.env.example</code> to <code>.env</code> and configure environment variables</li>
<li>Start the DashCaddy API: <code>npm run start</code> (or use your process manager like systemd or PM2)</li>
<li>Serve the dashboard through Caddy by adding a reverse proxy rule to your Caddyfile</li>
</ol>
<h2>Common installation issues</h2>
<p>If you encounter problems during installation, check this troubleshooting table:</p>
<div className="overflow-x-auto">
<table className="w-full text-left text-sm">
<thead>
<tr className="border-b border-surface-700">
<th className="py-3 pr-4 font-semibold text-surface-200">Symptom</th>
<th className="py-3 pr-4 font-semibold text-surface-200">Cause</th>
<th className="py-3 font-semibold text-surface-200">Solution</th>
</tr>
</thead>
<tbody className="text-surface-300">
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">Port 80 or 443 already in use</td>
<td className="py-3 pr-4">Another web server (nginx, Apache) is running</td>
<td className="py-3">Stop the conflicting service or change Caddy's ports in docker-compose.yml</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">Docker permission denied</td>
<td className="py-3 pr-4">Current user not in docker group</td>
<td className="py-3">Run <code>sudo usermod -aG docker $USER</code> and log out/in</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">Caddy Admin API unreachable</td>
<td className="py-3 pr-4">Caddy not running or Admin API disabled</td>
<td className="py-3">Ensure Caddy is running with <code>admin :2019</code> in its config</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">Technitium API token invalid</td>
<td className="py-3 pr-4">Token not set or expired</td>
<td className="py-3">Generate a new token in Technitium web console and update .env</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">Dashboard shows 502 Bad Gateway</td>
<td className="py-3 pr-4">API server not responding</td>
<td className="py-3">Check <code>docker logs dashcaddy-api</code> for errors</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">Out of memory during deployment</td>
<td className="py-3 pr-4">Insufficient RAM for container workloads</td>
<td className="py-3">Increase host RAM or reduce concurrent service deployments</td>
</tr>
<tr>
<td className="py-3 pr-4">Certificate trust errors in browser</td>
<td className="py-3 pr-4">DashCA root cert not installed on client</td>
<td className="py-3">Download root cert from DashCA page and install on client device</td>
</tr>
</tbody>
</table>
</div>
<h2>Post-install verification</h2>
<p>After the stack is up, verify each layer with these commands:</p>
<h3>1. Check container status</h3>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`docker ps --filter "name=dashcaddy"
# Expected: dashcaddy-api, dashcaddy-dashboard, caddy, technitium all running`}</code></pre>
<h3>2. Verify API health</h3>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`curl http://localhost:3001/healthz
# Expected: {"status":"ok","version":"1.0.0"}
curl http://localhost:3001/readyz
# Expected: {"status":"ready","checks":{"caddy":true,"technitium":true}}`}</code></pre>
<h3>3. Test Caddy Admin API</h3>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`curl http://localhost:2019/config/
# Expected: JSON configuration object`}</code></pre>
<h3>4. Verify Technitium DNS</h3>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`curl http://localhost:5380/api/dns/zones/list?token=YOUR_TOKEN
# Expected: List of DNS zones`}</code></pre>
<h3>5. Check Prometheus metrics</h3>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`curl http://localhost:3001/metrics
# Expected: Prometheus-formatted metrics output`}</code></pre>
<h3>6. Access the dashboard</h3>
<p>Open your browser and navigate to <code>http://localhost:3000</code> (or your configured domain). You should see the DashCaddy dashboard login screen.</p>
<h2>Next steps</h2>
<p>
Once install checks pass, head to the <a href="/docs/first-service" className="text-brand-400 hover:text-brand-300 underline">Deploy Your First Service</a> guide to
bring your first application online.
</p>
<p>
If you need to understand the architecture in more depth, see the <a href="/docs/overview" className="text-brand-400 hover:text-brand-300 underline">Product Overview</a>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+299
View File
@@ -0,0 +1,299 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export default function DocsIntegrationsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Infrastructure Integrations"
intro="DashCaddy is most valuable when its supporting integrations are healthy. This guide explains each layer it expects to work with, what it does, and how the pieces fit together into a single control plane."
>
<p>
DashCaddy is not a monolith. It is an orchestration layer that drives several independent infrastructure
components a container runtime, a reverse proxy, a DNS server, a certificate authority, a private network,
a metrics pipeline, and an AI surface. Each integration is swappable, observable, and independently debuggable.
When you understand what each layer is responsible for, you can pinpoint failures in minutes instead of guessing.
</p>
<p>
This guide walks every integration in depth: what it does, how DashCaddy talks to it, the configuration it
expects, and a code example where relevant. Read it end-to-end once, then come back to specific sections when
something goes wrong. For a quick diagnostic flow, see the <a href="/docs/troubleshooting">Troubleshooting</a> guide.
</p>
<h2>Docker container runtime</h2>
<p>
Docker (and Docker Compose) is the runtime foundation for every deployment workflow, container lifecycle action,
service discovery sweep, and template-based launch. DashCaddy communicates with the Docker daemon over the
Unix socket (<code>/var/run/docker.sock</code>) to start, stop, restart, inspect, and adopt containers, and to
deploy the <strong>92+ one-click application templates</strong> from the catalog.
</p>
<p>
The daemon connection is established at startup. If the socket is missing or permissioned for a different user,
DashCaddy&apos;s <code>/readyz</code> probe will fail immediately a fast signal that the runtime layer is broken.
During the <strong>Smart Defaults Wizard</strong>, DashCaddy probes the socket, reports the Docker version, and
suggests socket paths if the default is not found.
</p>
<p>
Optional <strong>Docker Swarm</strong> support (Premium) extends the same model across multiple nodes. When
Swarm mode is enabled, DashCaddy switches from single-container operations to service-level operations, managing
placement, replicas, and rolling updates across the cluster. See <a href="/docs/premium">Premium Features</a>.
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Verify the Docker socket DashCaddy will use
docker version
ls -l /var/run/docker.sock
# The DashCaddy container needs the socket mounted:
docker run -d \\
-v /var/run/docker.sock:/var/run/docker.sock \\
-p 3000:3000 \\
ghcr.io/dashcaddy/dashcaddy:latest`}</code></pre>
<blockquote className="border-l-4 border-brand-500/50 bg-brand-500/5 p-4 rounded-r-lg">
<p className="text-surface-300">
<strong className="text-brand-400">Note:</strong> Mounting the Docker socket grants full container control.
In production, run DashCaddy behind Tailscale or a firewall so the dashboard is not exposed to the public
internet.
</p>
</blockquote>
<h2>Caddy reverse proxy &amp; automatic HTTPS</h2>
<p>
Caddy is the reverse proxy and automatic HTTPS layer. Every service you publish through DashCaddy gets a Caddy
route that terminates TLS and proxies traffic to the upstream container. DashCaddy communicates with the
<strong> Caddy Admin API</strong> (default <code>localhost:2019</code>) to create, update, and remove routes,
and to trigger certificate issuance on demand.
</p>
<p>
Caddy&apos;s built-in internal CA auto-generates and renews certificates for every published service. For
public domains, Caddy can also use ACME (Let&apos;s Encrypt / ZeroSSL) automatically. The choice between
internal and public CA is made per-service at publish time, so you can mix internet-facing and lab services
on the same host without conflict.
</p>
<h3>Caddyfile-as-Code</h3>
<p>
Instead of hand-editing Caddyfiles, DashCaddy exposes a <strong>visual Caddyfile-as-Code builder</strong>. You
describe the desired route hostname, upstream, TLS options, headers, redirects, compression and DashCaddy
generates the valid Caddy configuration and applies it atomically through the Admin API. Configuration is
versioned and reviewable, so every change is auditable and reversible.
</p>
<p>
The generated config is rendered in the service&apos;s <strong>Caddyfile-as-Code view</strong>, so you can
inspect exactly what Caddy will receive before it is applied. If a route misbehaves, compare the rendered config
against your expectation. Invalid configs are rejected before they reach Caddy, preventing the proxy from
reloading into a broken state.
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Example generated Caddyfile (internal CA, lab hostname)
media.lab {
tls internal
encode zstd gzip
reverse_proxy localhost:8096 {
header_up X-Forwarded-Host {host}
header_up X-Real-IP {remote_host}
}
header {
Strict-Transport-Security "max-age=31536000"
X-Content-Type-Options nosniff
}
}`}</code></pre>
<h2>Technitium DNS DNS automation</h2>
<p>
Technitium DNS is the DNS automation target for record creation and removal. When you deploy or adopt a service,
DashCaddy creates the corresponding A or CNAME record through the Technitium REST API so the new hostname
resolves immediately. Removing a service cleans up the record automatically no orphaned DNS entries.
</p>
<p>
DashCaddy needs three pieces of information to drive Technitium: the server URL, an API token with write access
to the target zone, and the zone name itself. All three are configured during the Smart Defaults Wizard or
later under <strong>Settings DNS</strong>. A common failure mode is a token with the wrong scope it can
read records but not create them which fails silently. Always verify the token can write to the zone you
intend to use.
</p>
<p>
Internal zones (e.g. <code>.lab</code>) only resolve if the client uses Technitium as its resolver. Public
resolvers like 8.8.8.8 will not know about them. For remote clients, either point their DNS at Technitium
directly or use <strong>Tailscale</strong> with a MagicDNS / split-DNS setup.
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Create a record directly via the Technitium API (debugging)
curl -X POST "http://technitium-host:5380/api/zones/records/add" \\
-d "token=***" \\
-d "zone=lab" \\
-d "domain=media.lab" \\
-d "type=A" \\
-d "ipAddress=192.168.1.50"
# Verify the record resolves through Technitium
dig @technitium-host media.lab +short`}</code></pre>
<h2>DashCA internal certificate authority</h2>
<p>
DashCA is the certificate distribution system that makes internal HTTPS practical. Caddy&apos;s internal CA
issues certificates automatically for <code>.lab</code> and other private hostnames; DashCA provides the
distribution page where you download the root certificate and install it as a trusted CA across your devices.
Once trusted, every internal service is served over valid HTTPS with no browser warnings.
</p>
<p>
The root certificate must be installed on <strong>each client device</strong> that will access internal
services not just the server. A macOS laptop, a Windows desktop, and an Android phone each need the cert
installed separately. The DashCA page includes per-platform instructions (macOS Keychain, Windows certmgr,
Linux <code>update-ca-certificates</code>, and mobile profiles) to make this straightforward.
</p>
<p>
After installing the root CA, restart your browser or clear its certificate cache. Chrome and Firefox maintain
separate trust stores on some platforms; Firefox may need the import done from within its own settings.
</p>
<h2>Tailscale private access</h2>
<p>
DashCaddy fits naturally into private access patterns with <strong>Tailscale</strong>. Services can be published
only on a Tailnet, keeping them off the public internet while still benefiting from DashCaddy&apos;s DNS, proxy,
and TLS automation. This is ideal for home labs, internal team tools, and any service that should never be
internet-facing.
</p>
<p>
The typical setup runs Tailscale on the DashCaddy host, advertises the host on the Tailnet, and optionally
enables MagicDNS so Tailnet hostnames resolve without a separate DNS server. Combine with Technitium split-DNS
for the most seamless experience: Technitium handles <code>.lab</code> zones for Tailnet clients, while public
domains resolve normally.
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Install and authenticate Tailscale on the DashCaddy host
curl -fsSL https://tailscale.com/install.sh | sh
tailscale up --advertise-routes=192.168.1.0/24 --accept-routes
# Verify the host is on the Tailnet
tailscale status
tailscale ip
# From another Tailnet device, reach the service directly
curl -k https://dashcaddy-host.tailnet-name.ts.net/media.lab`}</code></pre>
<blockquote className="border-l-4 border-brand-500/50 bg-brand-500/5 p-4 rounded-r-lg">
<p className="text-surface-300">
<strong className="text-brand-400">Tip:</strong> If you publish services only on the Tailnet, set Caddy to
bind to the Tailscale interface IP rather than <code>0.0.0.0</code>. This guarantees the service is
unreachable from the LAN even if the firewall is misconfigured.
</p>
</blockquote>
<h2>Prometheus &amp; Grafana metrics &amp; observability</h2>
<p>
DashCaddy exports metrics in Prometheus format at <code>/metrics</code>, including service health, container
status, request counts, certificate expiry, and system resource indicators. Point your Prometheus scraper at
the endpoint and build Grafana dashboards on top for long-term observability, capacity planning, and alerting.
</p>
<p>
The metrics endpoint is unauthenticated by default for internal scraping. If your Prometheus instance is on a
different host or network, place it behind the same Tailscale Tailnet or restrict access with a reverse-proxy
basic-auth rule in Caddy.
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# prometheus.yml — scrape DashCaddy
scrape_configs:
- job_name: 'dashcaddy'
metrics_path: /metrics
static_configs:
- targets: ['dashcaddy-host:3000']
# Optional: increase scrape frequency for faster alerting
scrape_interval: 15s
scrape_timeout: 10s`}</code></pre>
<p>
Useful PromQL starters once data is flowing: <code>dashcaddy_service_health == 0</code> (unhealthy services),
<code> rate(dashcaddy_http_requests_total[5m])</code> (request throughput), and
<code>dashcaddy_cert_expiry_days &lt; 14</code> (certificates expiring soon).
</p>
<h2>MCP Server &amp; AI assistants</h2>
<p>
The built-in <strong>MCP (Model Context Protocol) Server</strong> exposes DashCaddy operations to AI assistants
and external automation. Combined with the <strong>AI Intent Router</strong>, you can issue natural-language
commands &ldquo;restart the media server&rdquo;, &ldquo;deploy the postgres template&rdquo;, &ldquo;is the
database healthy?&rdquo; and have DashCaddy execute the real infrastructure action through the standard MCP
tool interface.
</p>
<p>
This turns DashCaddy into an AI-operable control plane: the same operations available in the dashboard are
available as MCP tools, so an assistant like Claude or GPT can inspect and manage your infrastructure directly.
Full setup instructions, the tool catalog, and intent examples are in the <a href="/docs/api">API and Automation</a> guide.
</p>
<h2>How the layers fit together</h2>
<p>
The table below maps each integration to the layer it provides and the DashCaddy feature that consumes it.
When a service fails, locate the row whose symptom matches, then debug that integration directly.
</p>
<table>
<thead>
<tr>
<th>Layer</th>
<th>Integration</th>
<th>DashCaddy feature that uses it</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Runtime</strong></td>
<td>Docker / Docker Compose</td>
<td>Deploy, adopt, lifecycle, templates, service discovery</td>
</tr>
<tr>
<td><strong>Proxy</strong></td>
<td>Caddy (Admin API)</td>
<td>Reverse proxy routes, Caddyfile-as-Code, auto HTTPS</td>
</tr>
<tr>
<td><strong>DNS</strong></td>
<td>Technitium DNS</td>
<td>Automatic A/CNAME record creation &amp; cleanup</td>
</tr>
<tr>
<td><strong>Trust</strong></td>
<td>DashCA (internal CA)</td>
<td>Root certificate distribution for internal HTTPS</td>
</tr>
<tr>
<td><strong>Access</strong></td>
<td>Tailscale</td>
<td>Private networking, Tailnet-only publishing</td>
</tr>
<tr>
<td><strong>Observe</strong></td>
<td>Prometheus / Grafana</td>
<td>Metrics export, alerting, long-term dashboards</td>
</tr>
<tr>
<td><strong>Automate</strong></td>
<td>MCP Server + AI Intent Router</td>
<td>Natural-language ops, AI assistant tool surface</td>
</tr>
<tr>
<td><strong>Secure</strong></td>
<td>Security Center + audit log</td>
<td>Event aggregation, change auditing, RBAC</td>
</tr>
</tbody>
</table>
<h2>Operational flow: deploy to observe</h2>
<p>
When everything is wired correctly, a single service publish triggers the full chain automatically:
</p>
<ol>
<li><strong>Deploy / Adopt</strong> Docker runs the container (or adopts an existing one).</li>
<li><strong>Publish</strong> Caddy creates the reverse-proxy route and requests a TLS certificate.</li>
<li><strong>Resolve</strong> Technitium DNS creates the hostname record so the domain resolves.</li>
<li><strong>Trust</strong> DashCA distributes the root CA so clients accept the internal cert.</li>
<li><strong>Observe</strong> health checks, Prometheus metrics, and WebSocket live updates report state.</li>
<li><strong>Secure</strong> Security Center aggregates events; audit logging records every change.</li>
</ol>
<p>
Each step is independently observable. If a service is unreachable, walk the chain in order the first broken
step is your failure. For the full diagnostic procedure, see <a href="/docs/troubleshooting">Troubleshooting</a>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+286
View File
@@ -0,0 +1,286 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export default function DocsOverviewPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Product Overview"
intro="DashCaddy is a self-hosted control plane for deploying, exposing, and managing Docker applications — with automatic DNS, reverse proxy, internal HTTPS, real-time monitoring, AI-driven operations, and centralized fleet visibility."
>
<h2>What you'll learn</h2>
<blockquote className="border-l-4 border-brand-500/50 bg-brand-500/5 p-4 rounded-r-lg">
<p className="text-surface-300">
<strong className="text-brand-400">What you'll learn:</strong> This page covers the full DashCaddy architecture, design philosophy, component breakdown, and how it compares to manual self-hosting. By the end, you'll understand why DashCaddy exists, what problems it solves, and how its layers work together as a unified platform.
</p>
</blockquote>
<h2>What DashCaddy is</h2>
<p>
DashCaddy brings together the layers that self-hosters usually wire by hand — Docker deployment,
Caddy reverse proxy, Technitium DNS automation, internal certificate distribution, service monitoring,
and operational tooling — and unifies them behind a single, cohesive control plane.
</p>
<p>
Instead of editing a dozen config files and praying the layers agree, you deploy a service once and
DashCaddy wires the runtime, the proxy route, the DNS record, the TLS certificate, and the health
checks for you. The goal is simple: make running self-hosted services feel like one product instead
of six fragile integrations.
</p>
<h2>Platform architecture</h2>
<p>
DashCaddy is a production-grade platform built on eight distinct layers, each responsible for a
specific concern. Together they form a complete self-hosting stack that replaces dozens of manual
configuration steps with a single declarative action.
</p>
<h3>1. Application Layer</h3>
<p>
The Application Layer is what operators interact with directly. It is a React-based dashboard that
provides real-time visibility into every service, container, and infrastructure component managed by
DashCaddy. Beyond the visual interface, this layer exposes a REST API under <code>/api/v1/</code> and
a WebSocket channel for live updates. Every action available in the UI — deploying a service, editing
a Caddy route, reviewing audit logs — is available through the API, making the dashboard a thin client
over a fully programmable control plane. The application layer also handles authentication, role-based
access control, TOTP two-factor enrollment, and multi-user admin invitations.
</p>
<h3>2. Orchestration Layer</h3>
<p>
The Orchestration Layer is the Node.js/Express engine at the heart of DashCaddy. It receives deployment
requests, coordinates Docker container lifecycle, drives Caddy reverse proxy configuration through the
Admin API, manages Technitium DNS records programmatically, and handles certificate issuance and renewal.
This layer is responsible for ensuring that every deployment is atomic — either all layers succeed or the
operation rolls back cleanly. It maintains the authoritative service state store, tracks health checks,
and publishes events over WebSocket for the dashboard. The orchestration engine also powers the Smart
Defaults Wizard, Service Discovery, and the Caddyfile-as-Code builder.
</p>
<h3>3. Runtime Layer</h3>
<p>
The Runtime Layer is Docker and Docker Compose — the container workloads that actually run your services.
DashCaddy manages container creation, network attachment, volume mounts, environment variable injection,
and lifecycle operations (start, stop, restart, remove). For advanced deployments, the platform supports
Docker Swarm for multi-host orchestration and Fleet Management for coordinating services across multiple
servers. Every container managed by DashCaddy is tracked in the service state store, enabling features
like Service Discovery (adopting existing containers) and Disaster Recovery (full-system backup and restore
with SHA-256 checksum verification).
</p>
<h3>4. Edge Layer</h3>
<p>
The Edge Layer is Caddy — the reverse proxy that terminates HTTPS connections and routes traffic to your
services. DashCaddy manages Caddy entirely through its Admin API, never requiring manual edits to a
Caddyfile. The Caddyfile-as-Code builder generates configuration declaratively, and the orchestration
layer applies changes atomically. Caddy handles automatic TLS certificate issuance and renewal using its
built-in ACME client for public domains or its internal CA for private networks. The Edge Layer also
provides load balancing, header manipulation, request logging, and rate limiting — all configurable
through the DashCaddy dashboard without touching Caddy's native configuration syntax.
</p>
<h3>5. Name Resolution Layer</h3>
<p>
The Name Resolution Layer is Technitium DNS a self-hosted authoritative DNS server that DashCaddy
controls programmatically. When you deploy a service with hostname <code>plex.local</code>, the
orchestration layer creates an A record pointing to your host's IP address automatically. When you
remove the service, the record is cleaned up. This eliminates the manual DNS management that plagues
most self-hosting setups. Technitium DNS also supports zone transfers, forwarding, and custom record
types for advanced networking scenarios. The integration is optional — if you don't configure DNS,
DashCaddy skips this layer and your services still deploy with direct IP access.
</p>
<h3>6. Trust Layer</h3>
<p>
The Trust Layer handles certificate authority management and internal HTTPS distribution. Caddy's built-in
CA issues certificates automatically for every service, but those certificates are only trusted if the
client device trusts the issuing CA. DashCaddy solves this with DashCA — an internal certificate authority
distribution surface. The Smart Defaults Wizard initializes DashCA on first launch and offers the root
certificate for download. Once installed on client devices (browsers, phones, IoT devices), every service
managed by DashCaddy presents a trusted HTTPS connection without certificate warnings. This layer also
handles certificate renewal tracking and expiration alerts.
</p>
<h3>7. Observability Layer</h3>
<p>
The Observability Layer provides real-time health monitoring, structured audit logging, and metrics export.
Every service managed by DashCaddy has a health check that runs continuously, with status updates pushed
to the dashboard over WebSocket. The layer exports Prometheus-compatible metrics at <code>/metrics</code>,
enabling integration with Grafana, VictoriaMetrics, or any Prometheus-compatible monitoring stack. Audit
logs capture every administrative action — who deployed what, when, and from which IP — providing the
accountability required for multi-user environments. The Security Center aggregates logs from multiple
sources (Caddy access logs, container stdout, authentication events) into a unified event pipeline for
threat detection and forensic analysis.
</p>
<h3>8. Intelligence Layer</h3>
<p>
The Intelligence Layer makes DashCaddy AI-native. The AI Intent Router accepts natural-language commands
like "deploy Plex on port 32400" or "show me all unhealthy services" and translates them into API calls.
The MCP (Model Context Protocol) Server exposes DashCaddy operations to external AI assistants — Claude,
ChatGPT, or any MCP-compatible client can deploy services, check health, or modify configuration through
the protocol. This layer also powers the Plugin system, allowing third-party extensions to hook into
DashCaddy's event stream and extend functionality without modifying core code.
</p>
<h2>Design philosophy</h2>
<p>
DashCaddy is built on three principles that guide every architectural decision:
</p>
<ul>
<li>
<strong>Atomic operations:</strong> Deploying a service should succeed or fail as a single transaction.
You should never end up with a container running but no DNS record, or a Caddy route pointing to a
container that doesn't exist. The orchestration layer coordinates all layers and rolls back on failure.
</li>
<li>
<strong>Declarative configuration:</strong> You describe what you want (hostname, port, image), and
DashCaddy figures out the implementation details (Caddy route, DNS record, certificate). The
Caddyfile-as-Code builder generates configuration from your intent, not the other way around.
</li>
<li>
<strong>Progressive disclosure:</strong> The Smart Defaults Wizard gets you running in minutes with
sensible defaults. Advanced users can customize every layer — Caddy headers, DNS record types,
container resource limits — but complexity is optional, not mandatory.
</li>
</ul>
<h2>Comparison: Manual vs. DashCaddy Free vs. DashCaddy Premium</h2>
<div className="overflow-x-auto">
<table className="w-full text-left text-sm">
<thead>
<tr className="border-b border-surface-700">
<th className="py-3 pr-4 font-semibold text-surface-200">Capability</th>
<th className="py-3 pr-4 font-semibold text-surface-200">Manual Setup</th>
<th className="py-3 pr-4 font-semibold text-surface-200">DashCaddy Free</th>
<th className="py-3 font-semibold text-surface-200">DashCaddy Premium</th>
</tr>
</thead>
<tbody className="text-surface-300">
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">Docker deployment</td>
<td className="py-3 pr-4">Manual compose files</td>
<td className="py-3 pr-4">92+ one-click templates</td>
<td className="py-3">Templates + Recipes</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">Reverse proxy</td>
<td className="py-3 pr-4">Hand-edit Caddyfile</td>
<td className="py-3 pr-4">Caddyfile-as-Code builder</td>
<td className="py-3">Same + fleet-wide routes</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">DNS automation</td>
<td className="py-3 pr-4">Manual record creation</td>
<td className="py-3 pr-4">Technitium integration</td>
<td className="py-3">Same + multi-zone</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">TLS certificates</td>
<td className="py-3 pr-4">Let's Encrypt / manual</td>
<td className="py-3 pr-4">Automatic via Caddy + DashCA</td>
<td className="py-3">Same</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">Monitoring</td>
<td className="py-3 pr-4">Custom scripts</td>
<td className="py-3 pr-4">Real-time health + Prometheus</td>
<td className="py-3">Same + fleet dashboard</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">Multi-user access</td>
<td className="py-3 pr-4">None</td>
<td className="py-3 pr-4">TOTP 2FA + RBAC</td>
<td className="py-3">SSO (OIDC/SAML)</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">Backup &amp; recovery</td>
<td className="py-3 pr-4">Manual snapshots</td>
<td className="py-3 pr-4">One-click backup/restore</td>
<td className="py-3">Same + scheduled</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">AI operations</td>
<td className="py-3 pr-4">None</td>
<td className="py-3 pr-4">Intent Router + MCP Server</td>
<td className="py-3">Same</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">Multi-host orchestration</td>
<td className="py-3 pr-4">Manual Swarm/K8s</td>
<td className="py-3 pr-4">Single host</td>
<td className="py-3">Swarm + Fleet Management</td>
</tr>
<tr>
<td className="py-3 pr-4">Pricing</td>
<td className="py-3 pr-4">Time + complexity</td>
<td className="py-3 pr-4">Free forever</td>
<td className="py-3">$20$99 one-time</td>
</tr>
</tbody>
</table>
</div>
<h2>Real-world scenarios</h2>
<h3>Home lab operator</h3>
<p>
You run Plex, Nextcloud, Home Assistant, and a dozen other services on a single NUC. With DashCaddy,
you deploy each from a template, and every service gets a clean subdomain (<code>plex.local</code>,
<code>nextcloud.local</code>) with trusted HTTPS. Service Discovery adopts containers you already
had running, so you don't need to redeploy anything. The dashboard gives you one place to see health,
restart services, and review logs.
</p>
<h3>Small business internal tools</h3>
<p>
Your team needs Gitea, Grafana, and a wiki behind HTTPS with role-based access. DashCaddy Free handles
deployment, DNS, and certificates. TOTP 2FA and multi-user admin ensure only authorized team members
can modify infrastructure. Audit logs track who deployed what and when.
</p>
<h3>Multi-site fleet management</h3>
<p>
You manage DashCaddy instances across three offices. Premium's Fleet Management gives you a single
dashboard to monitor all hosts, deploy services to specific sites, and enforce configuration standards.
Swarm support lets you scale a service across multiple nodes within a site.
</p>
<h2>Premium licensing</h2>
<p>
DashCaddy Premium is a one-time purchase (not a subscription) that unlocks advanced orchestration
features. Pricing tiers:
</p>
<ul>
<li><strong>$20</strong> 30-day license</li>
<li><strong>$50</strong> 90-day license</li>
<li><strong>$70</strong> 180-day license</li>
<li><strong>$99</strong> 365-day license</li>
</ul>
<p>
Premium features include SSO (OIDC/SAML), Recipes (multi-service deployment blueprints), Docker Swarm
orchestration, and Fleet Management for multi-host coordination. The core platform is fully functional
without a license; Premium is for teams that need enterprise-grade access control and multi-site
visibility.
</p>
<h2>Next steps</h2>
<ul>
<li>
<a href="/docs/installation" className="text-brand-400 hover:text-brand-300 underline">
Installation Guide
</a> get DashCaddy running on your host with the guided installer or manual setup.
</li>
<li>
<a href="/docs/first-service" className="text-brand-400 hover:text-brand-300 underline">
Deploy Your First Service
</a> walk through deploying Plex (or any template) end-to-end.
</li>
</ul>
</DocsLayout>
<Footer />
</div>
);
}
+82 -389
View File
@@ -1,389 +1,82 @@
'use client';
import Link from 'next/link';
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
export default function DocsPage() {
return (
<div className="flex flex-col min-h-screen bg-surface-950 text-surface-50">
<Navbar />
{/* Hero Section */}
<section className="relative py-16 sm:py-20 lg:py-24">
<div className="absolute inset-0 -z-10">
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-96 h-96 bg-brand-500/20 rounded-full blur-3xl opacity-30 animate-pulse" />
</div>
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold mb-6">
Getting <span className="text-brand-400">Started</span>
</h1>
<p className="text-xl text-surface-300 max-w-2xl">
Get DashCaddy up and running on your server in just a few minutes.
</p>
</div>
</section>
{/* Main Content */}
<section className="relative py-12 sm:py-16 lg:py-20">
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
{/* Table of Contents */}
<div className="mb-16 rounded-lg border border-surface-700/50 bg-surface-800/50 p-8">
<h2 className="text-2xl font-bold text-surface-50 mb-6">Table of Contents</h2>
<ul className="space-y-3">
<li>
<a href="#prerequisites" className="text-brand-400 hover:text-brand-300 transition-colors flex items-center gap-2">
<span></span> Prerequisites
</a>
</li>
<li>
<a href="#installation" className="text-brand-400 hover:text-brand-300 transition-colors flex items-center gap-2">
<span></span> Installation
</a>
</li>
<li>
<a href="#configuration" className="text-brand-400 hover:text-brand-300 transition-colors flex items-center gap-2">
<span></span> Configuration
</a>
</li>
<li>
<a href="#first-run" className="text-brand-400 hover:text-brand-300 transition-colors flex items-center gap-2">
<span></span> First Run
</a>
</li>
<li>
<a href="#troubleshooting" className="text-brand-400 hover:text-brand-300 transition-colors flex items-center gap-2">
<span></span> Troubleshooting
</a>
</li>
</ul>
</div>
{/* Prerequisites Section */}
<section id="prerequisites" className="mb-16">
<h2 className="text-3xl font-bold text-surface-50 mb-6 flex items-center gap-3">
<span className="text-brand-400">📋</span> Prerequisites
</h2>
<p className="text-surface-300 mb-6 leading-relaxed">
Before you begin, ensure you have the following installed on your server:
</p>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6 space-y-4">
<div className="flex items-start gap-4">
<div className="text-xl flex-shrink-0">🐳</div>
<div>
<h3 className="font-semibold text-surface-50 mb-1">Docker</h3>
<p className="text-sm text-surface-400">Version 20.10+ required. <a href="https://docs.docker.com/get-docker/" className="text-brand-400 hover:text-brand-300">Install Docker</a></p>
</div>
</div>
<div className="flex items-start gap-4">
<div className="text-xl flex-shrink-0"></div>
<div>
<h3 className="font-semibold text-surface-50 mb-1">Caddy</h3>
<p className="text-sm text-surface-400">Version 2.7+ required. <a href="https://caddyserver.com/docs/install" className="text-brand-400 hover:text-brand-300">Install Caddy</a></p>
</div>
</div>
<div className="flex items-start gap-4">
<div className="text-xl flex-shrink-0">🟢</div>
<div>
<h3 className="font-semibold text-surface-50 mb-1">Node.js</h3>
<p className="text-sm text-surface-400">Version 18+ required. <a href="https://nodejs.org/" className="text-brand-400 hover:text-brand-300">Install Node.js</a></p>
</div>
</div>
<div className="flex items-start gap-4">
<div className="text-xl flex-shrink-0">🔧</div>
<div>
<h3 className="font-semibold text-surface-50 mb-1">Git</h3>
<p className="text-sm text-surface-400">For cloning the repository. <a href="https://git-scm.com/" className="text-brand-400 hover:text-brand-300">Install Git</a></p>
</div>
</div>
<div className="border-t border-surface-700/30 pt-4 mt-4">
<h3 className="font-semibold text-surface-50 mb-2">Optional</h3>
<div className="flex items-start gap-4">
<div className="text-xl flex-shrink-0">🌐</div>
<div>
<h3 className="font-semibold text-surface-50 mb-1">Technitium DNS</h3>
<p className="text-sm text-surface-400">For automatic DNS management. If not installed, you can still manage DNS manually.</p>
</div>
</div>
</div>
</div>
</section>
{/* Installation Section */}
<section id="installation" className="mb-16">
<h2 className="text-3xl font-bold text-surface-50 mb-6 flex items-center gap-3">
<span className="text-brand-400">📦</span> Installation
</h2>
<p className="text-surface-300 mb-8 leading-relaxed">
Follow these steps to install DashCaddy on your server:
</p>
{/* Step 1 */}
<div className="mb-10">
<h3 className="text-xl font-semibold text-surface-50 mb-4">Step 1: Clone the Repository</h3>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4">
<code className="text-sm text-green-400 font-mono block">
git clone https://github.com/dashcaddy/dashcaddy.git
<br />
cd dashcaddy
</code>
</div>
</div>
{/* Step 2 */}
<div className="mb-10">
<h3 className="text-xl font-semibold text-surface-50 mb-4">Step 2: Install Dependencies</h3>
<p className="text-surface-300 mb-4">Install Node.js dependencies:</p>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4">
<code className="text-sm text-green-400 font-mono block">
npm install
</code>
</div>
</div>
{/* Step 3 */}
<div className="mb-10">
<h3 className="text-xl font-semibold text-surface-50 mb-4">Step 3: Configure Environment Variables</h3>
<p className="text-surface-300 mb-4">Copy the example environment file and customize it:</p>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4 mb-4">
<code className="text-sm text-green-400 font-mono block">
cp .env.example .env
</code>
</div>
<p className="text-surface-300 mb-4">Then edit <code className="bg-surface-800 px-2 py-1 rounded text-brand-400">.env</code> with your configuration:</p>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4">
<code className="text-sm text-surface-300 font-mono whitespace-pre-wrap">
{`# Server
PORT=3000
NODE_ENV=production
# Database (optional - defaults to SQLite)
DATABASE_URL=sqlite:./data/dashcaddy.db
# Security
JWT_SECRET=your-secure-random-secret-here
SESSION_SECRET=another-secure-random-secret
# Caddy
CADDY_PORT=80
CADDY_HTTPS_PORT=443
CADDY_ADMIN_LISTEN=localhost:2019
# Technitium DNS (optional)
TECHNITIUM_API_URL=http://localhost:5380/api
TECHNITIUM_API_KEY=your-api-key
# Email (optional - for notifications)
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=your-email@example.com
SMTP_PASSWORD=your-password`}
</code>
</div>
</div>
{/* Step 4 */}
<div className="mb-10">
<h3 className="text-xl font-semibold text-surface-50 mb-4">Step 4: Build and Start</h3>
<p className="text-surface-300 mb-4">Build the application:</p>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4 mb-6">
<code className="text-sm text-green-400 font-mono block">
npm run build
</code>
</div>
<p className="text-surface-300 mb-4">Start DashCaddy:</p>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4">
<code className="text-sm text-green-400 font-mono block">
npm start
</code>
</div>
<p className="text-surface-300 mt-4 text-sm">
The application will be available at <code className="bg-surface-800 px-2 py-1 rounded text-brand-400">http://localhost:3000</code>
</p>
</div>
{/* Step 5 */}
<div className="mb-10">
<h3 className="text-xl font-semibold text-surface-50 mb-4">Step 5: Configure Caddy</h3>
<p className="text-surface-300 mb-4">Update your Caddy configuration to proxy requests to DashCaddy:</p>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4">
<code className="text-sm text-surface-300 font-mono whitespace-pre-wrap">
{`dashcaddy.local {
reverse_proxy localhost:3000
# Enable automatic HTTPS
encode gzip
# Security headers
header Strict-Transport-Security "max-age=31536000"
header X-Content-Type-Options "nosniff"
header X-Frame-Options "DENY"
}`}
</code>
</div>
</div>
</section>
{/* Configuration Section */}
<section id="configuration" className="mb-16">
<h2 className="text-3xl font-bold text-surface-50 mb-6 flex items-center gap-3">
<span className="text-brand-400"></span> Configuration
</h2>
<p className="text-surface-300 mb-8 leading-relaxed">
Key environment variables for DashCaddy configuration:
</p>
<div className="space-y-6">
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
<h3 className="font-semibold text-brand-400 mb-2 font-mono">PORT</h3>
<p className="text-surface-300 text-sm mb-2">The port DashCaddy runs on. Default: <code className="bg-surface-800 px-1 rounded">3000</code></p>
</div>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
<h3 className="font-semibold text-brand-400 mb-2 font-mono">JWT_SECRET</h3>
<p className="text-surface-300 text-sm mb-2">Secret key for JWT tokens. Generate a secure random string:</p>
<code className="bg-surface-800 px-2 py-1 rounded text-brand-400 text-xs">openssl rand -hex 32</code>
</div>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
<h3 className="font-semibold text-brand-400 mb-2 font-mono">DATABASE_URL</h3>
<p className="text-surface-300 text-sm">Connection string for your database. Defaults to SQLite if not provided.</p>
</div>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
<h3 className="font-semibold text-brand-400 mb-2 font-mono">CADDY_ADMIN_LISTEN</h3>
<p className="text-surface-300 text-sm mb-2">Caddy admin API endpoint. Default: <code className="bg-surface-800 px-1 rounded">localhost:2019</code></p>
</div>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
<h3 className="font-semibold text-brand-400 mb-2 font-mono">TECHNITIUM_API_URL</h3>
<p className="text-surface-300 text-sm">URL to your Technitium DNS API. Optional for DNS management features.</p>
</div>
</div>
</section>
{/* First Run Section */}
<section id="first-run" className="mb-16">
<h2 className="text-3xl font-bold text-surface-50 mb-6 flex items-center gap-3">
<span className="text-brand-400">🚀</span> First Run
</h2>
<div className="rounded-lg border border-brand-500/30 bg-brand-950/50 p-8">
<ol className="space-y-4 list-decimal list-inside text-surface-300">
<li>Access the dashboard at <code className="bg-surface-800 px-2 py-1 rounded text-brand-400">http://dashcaddy.local</code></li>
<li>Create your admin account with a strong password</li>
<li>Enable TOTP 2FA for enhanced security</li>
<li>Configure your Technitium DNS API key (optional)</li>
<li>Deploy your first application from the app templates library</li>
<li>Monitor your apps in real-time from the dashboard</li>
</ol>
</div>
</section>
{/* Troubleshooting Section */}
<section id="troubleshooting" className="mb-16">
<h2 className="text-3xl font-bold text-surface-50 mb-6 flex items-center gap-3">
<span className="text-brand-400">🔧</span> Troubleshooting
</h2>
<div className="space-y-6">
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
<h3 className="font-semibold text-surface-50 mb-3 flex items-center gap-2">
<span className="text-red-400"></span> Docker daemon not running
</h3>
<p className="text-surface-300 text-sm mb-3">Make sure the Docker daemon is started:</p>
<code className="bg-surface-800 px-2 py-1 rounded text-green-400 text-xs block">sudo systemctl start docker</code>
</div>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
<h3 className="font-semibold text-surface-50 mb-3 flex items-center gap-2">
<span className="text-red-400"></span> Port 3000 already in use
</h3>
<p className="text-surface-300 text-sm mb-3">Change the PORT in your .env file or stop the process using that port:</p>
<code className="bg-surface-800 px-2 py-1 rounded text-green-400 text-xs block">lsof -i :3000</code>
</div>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
<h3 className="font-semibold text-surface-50 mb-3 flex items-center gap-2">
<span className="text-red-400"></span> Cannot connect to Caddy admin API
</h3>
<p className="text-surface-300 text-sm mb-3">Verify Caddy is running and the admin API is accessible:</p>
<code className="bg-surface-800 px-2 py-1 rounded text-green-400 text-xs block">curl http://localhost:2019/config/</code>
</div>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
<h3 className="font-semibold text-surface-50 mb-3 flex items-center gap-2">
<span className="text-red-400"></span> Database connection errors
</h3>
<p className="text-surface-300 text-sm mb-3">Check that your DATABASE_URL is correct and the database is accessible. For SQLite, ensure the data directory exists:</p>
<code className="bg-surface-800 px-2 py-1 rounded text-green-400 text-xs block">mkdir -p ./data</code>
</div>
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
<h3 className="font-semibold text-surface-50 mb-3 flex items-center gap-2">
<span className="text-red-400"></span> SSL certificate issues
</h3>
<p className="text-surface-300 text-sm mb-3">DashCaddy uses Caddy's internal CA for certificate generation. If you have issues, check the Caddy logs:</p>
<code className="bg-surface-800 px-2 py-1 rounded text-green-400 text-xs block">journalctl -u caddy -f</code>
</div>
</div>
</section>
{/* Next Steps */}
<section className="mb-16">
<div className="rounded-lg border border-surface-700/50 bg-surface-800/50 p-8">
<h2 className="text-2xl font-bold text-surface-50 mb-6">Next Steps</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<Link
href="/features"
className="flex items-start gap-4 p-4 rounded-lg border border-brand-500/20 bg-brand-950/30 hover:bg-brand-950/50 transition-colors"
>
<span className="text-2xl flex-shrink-0"></span>
<div>
<h3 className="font-semibold text-surface-50 mb-1">Explore Features</h3>
<p className="text-sm text-surface-400">Learn about all the powerful features DashCaddy offers.</p>
</div>
</Link>
<a
href="#"
className="flex items-start gap-4 p-4 rounded-lg border border-surface-700/50 bg-surface-800/50 hover:bg-surface-800 transition-colors"
>
<span className="text-2xl flex-shrink-0">📚</span>
<div>
<h3 className="font-semibold text-surface-50 mb-1">API Reference</h3>
<p className="text-sm text-surface-400">Full API documentation for developers.</p>
</div>
</a>
<Link
href="/pricing"
className="flex items-start gap-4 p-4 rounded-lg border border-surface-700/50 bg-surface-800/50 hover:bg-surface-800 transition-colors"
>
<span className="text-2xl flex-shrink-0">💎</span>
<div>
<h3 className="font-semibold text-surface-50 mb-1">View Pricing</h3>
<p className="text-sm text-surface-400">Check out our free and premium plans.</p>
</div>
</Link>
<a
href="mailto:support@dashcaddy.net"
className="flex items-start gap-4 p-4 rounded-lg border border-surface-700/50 bg-surface-800/50 hover:bg-surface-800 transition-colors"
>
<span className="text-2xl flex-shrink-0">💬</span>
<div>
<h3 className="font-semibold text-surface-50 mb-1">Get Support</h3>
<p className="text-sm text-surface-400">Contact our support team for help.</p>
</div>
</a>
</div>
</div>
</section>
</div>
</section>
<Footer />
</div>
);
}
import Link from 'next/link';
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
const docs = [
{
href: '/docs/overview',
title: 'Product Overview',
description: 'What DashCaddy is, who it is for, and how the platform fits together.',
},
{
href: '/docs/installation',
title: 'Installation Guide',
description: 'Installer-based and manual setup paths, prerequisites, and first launch expectations.',
},
{
href: '/docs/first-service',
title: 'Deploy Your First Service',
description: 'Learn the actual deployment flow and how DashCaddy wires Docker, DNS, and Caddy together.',
},
{
href: '/docs/integrations',
title: 'Infrastructure Integrations',
description: 'How DashCaddy works with Docker, Caddy, Technitium DNS, DashCA, and private access workflows.',
},
{
href: '/docs/premium',
title: 'Premium Features',
description: 'Free vs Premium, current plan model, and the exact premium-gated feature set.',
},
{
href: '/docs/api',
title: 'API and Automation',
description: 'How the API fits into service management, deployment workflows, and automation.',
},
{
href: '/docs/troubleshooting',
title: 'Troubleshooting',
description: 'A practical debugging guide for DNS, TLS, reverse proxy, certificates, and service health.',
},
];
export default function DocsHomePage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<section className="relative overflow-hidden py-16 sm:py-20 lg:py-24">
<div className="absolute inset-0 -z-10">
<div className="absolute left-1/2 top-1/2 h-96 w-96 -translate-x-1/2 -translate-y-1/2 rounded-full bg-brand-500/20 blur-3xl opacity-30" />
</div>
<div className="mx-auto max-w-5xl px-4 sm:px-6 lg:px-8">
<p className="mb-4 text-sm font-semibold uppercase tracking-[0.25em] text-brand-400">Documentation</p>
<h1 className="text-4xl font-bold tracking-tight sm:text-5xl lg:text-6xl">DashCaddy Docs</h1>
<p className="mt-6 max-w-3xl text-lg leading-8 text-surface-300">
Everything you need to understand, install, operate, and extend DashCaddy as a real self-hosting platform.
</p>
</div>
</section>
<section className="pb-20">
<div className="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3">
{docs.map((doc) => (
<Link
key={doc.href}
href={doc.href}
className="rounded-2xl border border-surface-700/50 bg-surface-900/50 p-6 transition-all hover:border-brand-500/50 hover:bg-surface-900"
>
<h2 className="text-xl font-semibold text-surface-50">{doc.title}</h2>
<p className="mt-3 text-sm leading-6 text-surface-300">{doc.description}</p>
<p className="mt-5 text-sm font-medium text-brand-400">Read guide </p>
</Link>
))}
</div>
</div>
</section>
<Footer />
</div>
);
}
+281
View File
@@ -0,0 +1,281 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export default function DocsPremiumPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Premium Features"
intro="DashCaddy keeps its Premium model intentionally narrow. The core platform — deployment, DNS, reverse proxy, HTTPS, monitoring, templates, service discovery, and the API — is fully useful without a license. Premium unlocks a focused set of advanced orchestration features."
>
<p>
DashCaddy&apos;s philosophy is that the day-to-day platform should be free forever. Everything you need to run
a single host the dashboard, the full template catalog, Caddy + DNS + TLS automation, real-time monitoring,
Prometheus metrics, multi-user accounts with 2FA and RBAC, the Security Center, the AI Intent Router, the MCP
Server, the JS SDK, and backup/restore works without a license. Premium adds four capabilities aimed at
teams and multi-host operators who need single sign-on, multi-container stacks, cluster orchestration, or
fleet-wide management.
</p>
<p>
This guide explains exactly what each Premium feature does, how it differs from the free tier, how to set it
up, and how pricing and licensing work. If you only ever run one host, you may never need Premium and
that&apos;s by design.
</p>
<h2>Free vs Premium at a glance</h2>
<p>
The comparison table below covers every major capability. &ldquo;Free&rdquo; means available on an unlicensed
install; &ldquo;Premium&rdquo; means the feature requires an active license.
</p>
<table>
<thead>
<tr>
<th>Capability</th>
<th>Free</th>
<th>Premium</th>
</tr>
</thead>
<tbody>
<tr><td>Dashboard &amp; web UI</td><td></td><td></td></tr>
<tr><td>92+ application templates</td><td></td><td></td></tr>
<tr><td>Caddy reverse proxy + auto HTTPS</td><td></td><td></td></tr>
<tr><td>Caddyfile-as-Code builder</td><td></td><td></td></tr>
<tr><td>Technitium DNS automation</td><td></td><td></td></tr>
<tr><td>DashCA internal certificate authority</td><td></td><td></td></tr>
<tr><td>Service Discovery</td><td></td><td></td></tr>
<tr><td>Real-time monitoring + WebSocket updates</td><td></td><td></td></tr>
<tr><td>Prometheus metrics endpoint</td><td></td><td></td></tr>
<tr><td>Multi-user accounts (invites, email magic link)</td><td></td><td></td></tr>
<tr><td>TOTP 2FA &amp; RBAC roles</td><td></td><td></td></tr>
<tr><td>Encrypted credential storage</td><td></td><td></td></tr>
<tr><td>Security Center &amp; audit logging</td><td></td><td></td></tr>
<tr><td>AI Intent Router &amp; MCP Server</td><td></td><td></td></tr>
<tr><td>REST API &amp; MCP Server</td><td></td><td></td></tr>
<tr><td>Backup / restore &amp; Disaster Recovery</td><td></td><td></td></tr>
<tr><td>Internationalization (5 languages)</td><td></td><td></td></tr>
<tr><td>Plugin &amp; extension system</td><td></td><td></td></tr>
<tr><td>Smart Defaults Wizard</td><td></td><td></td></tr>
<tr><td><strong>Auto-Login SSO</strong></td><td></td><td></td></tr>
<tr><td><strong>Recipes (multi-container stacks)</strong></td><td></td><td></td></tr>
<tr><td><strong>Docker Swarm orchestration</strong></td><td></td><td></td></tr>
<tr><td><strong>Multi-Host Fleet Management</strong></td><td></td><td></td></tr>
<tr><td>Priority support</td><td></td><td></td></tr>
</tbody>
</table>
<h2>Premium feature deep dive</h2>
<h3>Auto-Login SSO</h3>
<p>
<strong>Auto-Login SSO</strong> provides single sign-on across all services published through DashCaddy, so an
authenticated DashCaddy user reaches their apps without logging in again to each one. Once you sign into the
DashCaddy dashboard, SSO forwards a signed token to participating services that auto-authenticates the session.
This creates a seamless internal portal experience ideal for teams that want one front door to every tool.
</p>
<p>
SSO integrates with services that accept a shared authentication header or token exchange. Supported targets
include apps that read a configurable auth header (common in self-hosted dashboards, wikis, and admin panels)
as well as services that expose a login callback URL. The exact wiring is per-service: in the publish dialog,
enable <strong>SSO</strong> and provide the header name or callback endpoint the target expects. DashCaddy
handles token signing, rotation, and revocation.
</p>
<p>
SSO respects your existing RBAC roles. A user with read-only access in DashCaddy will be passed through to
services as a read-only identity where the target supports role mapping. Revoking a user in DashCaddy
immediately invalidates their SSO sessions across all linked services.
</p>
<blockquote className="border-l-4 border-brand-500/50 bg-brand-500/5 p-4 rounded-r-lg">
<p className="text-surface-300">
<strong className="text-brand-400">Note:</strong> SSO is a pass-through convenience layer, not a replacement
for per-service authentication. Services that require their own login (e.g. a database admin tool) will still
prompt unless they explicitly support header/token SSO.
</p>
</blockquote>
<h3>Recipes multi-container stacks</h3>
<p>
<strong>Recipes</strong> let you deploy multi-container application stacks as a single coordinated unit. A
Recipe bundles several templates together with pre-wired networking, shared volumes, environment variable
links, and startup ordering, so a complex stack comes up in one click instead of a dozen manual steps.
</p>
<p>
Typical Recipe stacks include an application plus its dependencies: a web app + PostgreSQL + Redis, a media
suite with its transcoder and metadata store, an analytics pipeline with a database and dashboard, or a
development environment with a code server, language runtime, and database. Each Recipe declares its components,
the network connections between them, and any secrets or config the stack needs at launch.
</p>
<p>
You can also <strong>create your own Recipes</strong>. Define the component templates, wire the internal
network (e.g. app <code>db:5432</code>), set environment variable references, and save the Recipe to your
catalog. Custom Recipes are versioned and shareable, so a team can standardize on the same stack definition
across hosts.
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Deploy a Recipe via the API
curl -X POST -H "Authorization: Bearer ***" \\
-H "Content-Type: application/json" \\
-d '{"recipe":"analytics-stack","name":"analytics"}' \\
https://dashcaddy-host/api/v1/recipes/deploy`}</code></pre>
<h3>Docker Swarm multi-node orchestration</h3>
<p>
<strong>Docker Swarm</strong> support extends DashCaddy&apos;s deployment model from a single host to a cluster.
Run services across a Swarm cluster instead of one machine, with DashCaddy managing placement, replicas,
rolling updates, routing, and TLS across every node. This is the right feature when a single host can no longer
carry the load or when you need redundancy for critical services.
</p>
<p>
Multi-node setup follows Docker&apos;s standard Swarm workflow: initialize the manager
(<code>docker swarm init</code>), join workers (<code>docker swarm join --token ... &lt;manager-ip&gt;</code>),
then enable Swarm mode in DashCaddy under <strong>Settings Cluster</strong>. DashCaddy detects the cluster
and switches from single-container operations to service-level operations deploy, scale, update, and rollback
all operate on Swarm services rather than individual containers.
</p>
<p>
Routing and TLS are handled cluster-wide: Caddy&apos;s ingress mesh routes traffic to the correct node, and
certificates are issued per published hostname regardless of which node the container lands on. DashCaddy&apos;s
service discovery tracks placement changes as the scheduler rebalances containers.
</p>
<h3>Multi-Host Fleet Management</h3>
<p>
<strong>Multi-Host Fleet Management</strong> lets you manage DashCaddy deployments across multiple hosts from
one control plane. Instead of opening a separate dashboard per server, you register every host in a single
fleet view and deploy, monitor, and operate services across the entire fleet with unified visibility. This is
designed for operators running DashCaddy on several physical boxes, VPSes, or edge locations.
</p>
<p>
The fleet workflow has three parts. <strong>Register hosts</strong> by installing the DashCaddy agent on each
machine and pairing it with your control plane each host reports its resources, running services, and health.
<strong> Health probes</strong> poll every host on an interval and surface failures (container down, disk full,
cert expiring) in a unified alert feed. <strong>Deploy plans</strong> let you target a service or Recipe at a
specific host or a group of hosts, so you can place the media stack on the box with GPU and the database on the
box with SSD without switching dashboards.
</p>
<p>
Fleet Management is distinct from Swarm: Swarm orchestrates containers across a single logical cluster, while
Fleet Management orchestrates DashCaddy instances across independent hosts. You can use both together a fleet
of hosts, some of which are themselves Swarm clusters.
</p>
<h2>Pricing</h2>
<p>
Premium is sold as one-time payments for fixed license durations. There is a single Premium tier no ladder
of plans to navigate. Longer durations are discounted relative to the monthly rate.
</p>
<table>
<thead>
<tr>
<th>Duration</th>
<th>Price</th>
<th>Effective monthly rate</th>
</tr>
</thead>
<tbody>
<tr><td>30 days</td><td><strong>$20</strong></td><td>~$20.00 / month</td></tr>
<tr><td>90 days</td><td><strong>$50</strong></td><td>~$16.67 / month</td></tr>
<tr><td>180 days</td><td><strong>$70</strong></td><td>~$11.67 / month</td></tr>
<tr><td>365 days</td><td><strong>$99</strong></td><td>~$8.25 / month</td></tr>
</tbody>
</table>
<p>
The 365-day plan offers the best value at roughly $8.25/month equivalent about 59% off the 30-day rate.
All durations unlock the identical Premium feature set; only the length and per-month cost differ.
</p>
<h2>Plan terms</h2>
<ul>
<li><strong>One-time payments</strong> no auto-renewing subscription; your license runs for the purchased duration and then expires.</li>
<li><strong>One Premium tier</strong> every duration unlocks the same features.</li>
<li><strong>One active machine per license</strong> a license is bound to a single host at a time.</li>
<li><strong>7-day grace period</strong> after expiry services keep running while you renew; Premium features are read-only during grace.</li>
<li><strong>No perpetual licenses</strong> Premium is term-based; the free tier is permanent.</li>
<li><strong>No free trial</strong> the free tier is comprehensive enough to evaluate the platform first.</li>
</ul>
<h2>License lifecycle</h2>
<p>
A license moves through a defined lifecycle from purchase to deactivation. Understanding this flow helps you
renew on time, move between hosts, and recover from validation failures.
</p>
<ol>
<li>
<strong>Purchase</strong> buy a duration from the dashboard
(<strong>Settings Licensing</strong>). You receive a license key tied to your account.
</li>
<li>
<strong>Activate</strong> on first launch with the key, DashCaddy contacts the licensing server and binds
the license to that machine. Premium features unlock immediately.
</li>
<li>
<strong>Periodic validation</strong> DashCaddy re-validates the license against the licensing server on
launch and at regular intervals thereafter. This keeps the license tied to one active machine and enables
clean deactivation.
</li>
<li>
<strong>Grace period (7 days)</strong> if the license expires or the server is unreachable, DashCaddy
enters a 7-day grace window. Your services keep running; Premium features become read-only. Renew or
reactivate during this window to restore full functionality.
</li>
<li>
<strong>Deactivate</strong> to move a license to a new host, deactivate it on the old machine from
<strong> Settings Licensing</strong>. This releases the binding so the key can be activated on the new host.
</li>
</ol>
<blockquote className="border-l-4 border-brand-500/50 bg-brand-500/5 p-4 rounded-r-lg">
<p className="text-surface-300">
<strong className="text-brand-400">Important:</strong> The one-active-machine limit is enforced by the
licensing server. If you reinstall the OS or replace the host without deactivating first, contact support to
release the stale binding.
</p>
</blockquote>
<h2>How to subscribe</h2>
<ol>
<li>Open the dashboard and go to <strong>Settings Licensing</strong>.</li>
<li>Choose a plan duration (30 / 90 / 180 / 365 days) and complete checkout.</li>
<li>Your license key is validated automatically Premium features unlock immediately.</li>
<li>Manage renewal, reactivation, and machine deactivation from the same panel.</li>
<li>If you migrate hosts, deactivate on the old machine before activating on the new one.</li>
</ol>
<h2>Frequently asked questions</h2>
<h3>Do I lose my services if my license expires?</h3>
<p>
No. Running services are never stopped by a license expiry. During the 7-day grace period everything keeps
running; Premium features become read-only. After grace, Premium-only features are disabled but the free tier
(including all your deployed services) continues to operate.
</p>
<h3>Can I use one license on multiple hosts?</h3>
<p>
No each license is bound to one active machine at a time. To move a license, deactivate it on the current
host and activate it on the new one. For managing multiple hosts simultaneously, use <strong>Fleet Management</strong>,
which is itself a Premium feature requiring a license per host you want under centralized control.
</p>
<h3>Is there a free trial?</h3>
<p>
No. The free tier is permanent and feature-rich you can evaluate the entire core platform without paying.
Premium adds orchestration features that you likely already know you need (SSO, Recipes, Swarm, Fleet).
</p>
<h3>What happens to my Premium Recipes and Swarm services if I let the license lapse?</h3>
<p>
They keep running under the grace period and continue to run as ordinary services after that. You lose the
ability to <em>modify</em> them through Premium tooling (e.g. redeploying a Recipe or scaling a Swarm service)
until you renew, but the workloads themselves are not destroyed.
</p>
<h3>How is the license validated?</h3>
<p>
DashCaddy contacts an external licensing server on launch and periodically thereafter. The host must be able
to reach the licensing server for validation to succeed. If the server is temporarily unreachable, the grace
period covers the gap.
</p>
<p>
Ready to upgrade? Head to <strong>Settings Licensing</strong> in your dashboard, or learn more about the
platform in the <a href="/docs/overview">Product Overview</a> and <a href="/docs/integrations">Integrations</a> guides.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+314
View File
@@ -0,0 +1,314 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export default function DocsTroubleshootingPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Troubleshooting"
intro="Because DashCaddy sits across runtime, DNS, reverse proxy, certificates, and dashboard state, the fastest way to debug it is layer by layer instead of guessing. This guide walks each layer with the common failures and fixes."
>
<p>
DashCaddy orchestrates several independent layers a container runtime, a DNS server, a reverse proxy, a
certificate authority, and its own API and dashboard. When a service is unreachable, the failure is almost
always in exactly one of these layers while the others are healthy. This guide gives you a structured,
layer-by-layer diagnostic procedure with the exact commands to run and the fixes to apply.
</p>
<p>
The single most important habit: <strong>localize before you fix</strong>. Resist the urge to restart
everything. Use the health endpoints to narrow down which layer is broken, then dig into that layer with the
commands below. You will solve problems far faster than by reloading the whole stack.
</p>
<h2>Health check endpoints start here</h2>
<p>
Every investigation begins with the built-in probes. They tell you whether the DashCaddy process itself is
healthy and whether its dependencies are wired up, in two seconds:
</p>
<ul>
<li><code>/healthz</code> <strong>liveness</strong>. Returns 200 if the DashCaddy process is up.</li>
<li><code>/readyz</code> <strong>readiness</strong>. Returns 200 only when DashCaddy can serve traffic, including connectivity to Docker, Caddy, and DNS where configured.</li>
</ul>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Print just the HTTP status codes
curl -s -o /dev/null -w "healthz: %{http_code}\\n" https://dashcaddy-host/healthz
curl -s -o /dev/null -w "readyz: %{http_code}\\n" https://dashcaddy-host/readyz`}</code></pre>
<p>
Interpret the result:
</p>
<ul>
<li><strong>Both 200</strong> DashCaddy and its dependencies are up. The problem is downstream of the platform (the service itself, DNS, cert trust, or the client).</li>
<li><strong><code>/healthz</code> 200, <code>/readyz</code> fails</strong> the process is up but a dependency is unreachable: Docker socket, Caddy Admin API, or Technitium DNS. Read the <code>/readyz</code> body for which dependency failed.</li>
<li><strong><code>/healthz</code> fails</strong> the DashCaddy process itself is down. Check <code>docker ps</code> and <code>docker logs dashcaddy</code>.</li>
</ul>
<h2>The debug order work bottom-up</h2>
<p>
When a specific service is unreachable, walk the stack from the container outward to the client. Each step
depends on the one before it, so the first failing step is your root cause:
</p>
<ol>
<li><strong>Backend container</strong> is it running and healthy? (<code>docker ps</code>, <code>docker logs</code>)</li>
<li><strong>Backend port</strong> is the service listening and reachable on the host? (<code>curl localhost:port</code>)</li>
<li><strong>Reverse proxy route</strong> did Caddy apply the route correctly? (Caddyfile-as-Code view, Admin API)</li>
<li><strong>DNS</strong> does the hostname resolve to the right host? (<code>dig</code>, <code>nslookup</code>)</li>
<li><strong>Certificate trust</strong> does the client trust the CA? (<code>openssl s_client</code>, browser cert store)</li>
<li><strong>Dashboard / API state</strong> does DashCaddy reflect reality? (compare UI vs. actual container state)</li>
</ol>
<p>
The sections below cover each layer in detail with the commands and fixes for the most common failures.
</p>
<h2>DNS issues</h2>
<p>
DNS problems show up as &ldquo;hostname does not resolve&rdquo; or &ldquo;resolves to the wrong address.&rdquo;
Because DashCaddy uses Technitium for internal zones, the most common cause is a client using a public resolver
that does not know about your private zones.
</p>
<ul>
<li><strong>Check</strong>: is the client using Technitium as its resolver? Public resolvers (8.8.8.8, 1.1.1.1) will not resolve internal <code>.lab</code> zones. Point the client&apos;s DNS at Technitium, or use Tailscale MagicDNS / split-DNS for remote clients.</li>
<li><strong>Check</strong>: is the record present in the correct zone? DNS automation fails silently when the zone name is wrong a record in <code>lab</code> vs <code>lab.</code> is a different zone.</li>
<li><strong>Check</strong>: is the Technitium API token valid and scoped for writes? An expired or read-only token will let records appear to &ldquo;work&rdquo; in the UI but fail to actually create.</li>
<li><strong>Fix</strong>: re-run the DNS step from the service&apos;s action menu, or recreate the record manually in Technitium and let DashCaddy reconcile.</li>
</ul>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Query Technitium directly (bypass the client's resolver)
dig @technitium-host media.lab +short
nslookup media.lab technitium-host
# Check what the client's resolver returns (may differ)
dig media.lab +short
# Trace the full resolution path
dig media.lab +trace`}</code></pre>
<p>
If <code>dig @technitium-host</code> returns the right IP but <code>dig media.lab</code> does not, the client
is not using Technitium. If Technitium itself returns nothing, the record was never created check the token
and zone, then recreate it.
</p>
<h2>TLS / certificate problems</h2>
<p>
Certificate problems show up as browser warnings (<code>NET::ERR_CERT_AUTHORITY_INVALID</code>) or TLS
handshake failures. There are two distinct causes, and the fix is different for each.
</p>
<h3>Cause 1: client does not trust the internal CA</h3>
<p>
For internal (<code>.lab</code>) services, Caddy uses its internal CA and DashCA distributes the root
certificate. The root cert must be installed as a trusted CA on <strong>each client device</strong> not just
the server. Download it from the <strong>DashCA</strong> page and follow the per-platform instructions (macOS
Keychain, Windows certmgr, Linux <code>update-ca-certificates</code>, mobile profiles).
</p>
<h3>Cause 2: certificate issuance failed</h3>
<p>
If Caddy could not reach its CA at deploy time (internal CA down, or ACME unreachable for public domains), no
certificate is issued and the TLS handshake fails outright. Confirm the Caddy Admin API is reachable, then
redeploy or re-trigger TLS for the service.
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Inspect the certificate a server presents
echo | openssl s_client -connect media.lab:443 -servername media.lab 2>/dev/null \\
| openssl x509 -noout -issuer -subject -dates
# Verify the chain against a specific CA bundle
openssl s_client -connect media.lab:443 -CAfile /path/to/dashca-root.crt </dev/null`}</code></pre>
<p>
If <code>openssl s_client</code> shows the issuer is Caddy&apos;s internal CA and your browser still warns,
the root cert is not installed on that client. If <code>s_client</code> shows no certificate at all, issuance
failed check Caddy.
</p>
<blockquote className="border-l-4 border-brand-500/50 bg-brand-500/5 p-4 rounded-r-lg">
<p className="text-surface-300">
<strong className="text-brand-400">Tip:</strong> After installing the root CA, restart the browser. Chrome
and Firefox maintain separate trust stores on some platforms Firefox may need the import done from its own
settings rather than the OS store.
</p>
</blockquote>
<h2>Reverse proxy debugging (Caddy)</h2>
<p>
If the service is up, the port is reachable, and DNS resolves, but the URL returns 502, 504, or does not route,
the problem is in the Caddy layer. DashCaddy drives Caddy through its Admin API, so two things can go wrong:
the Admin API is unreachable, or the generated config is wrong.
</p>
<ul>
<li><strong>Check</strong>: is the Caddy Admin API reachable from the DashCaddy API server? (<code>curl localhost:2019/config/</code> on the host)</li>
<li><strong>Check</strong>: does the Caddy route point at the correct upstream host:port? Use the <strong>Caddyfile-as-Code view</strong> to inspect the generated config.</li>
<li><strong>Check</strong>: Caddy logs <code>docker logs caddy</code> or your Caddy service logs for upstream connection errors and reload failures.</li>
<li><strong>Fix</strong>: re-apply the route from the service&apos;s action menu; DashCaddy reconciles the Caddy configuration atomically. If the config is invalid, DashCaddy rejects it before Caddy ever sees it.</li>
</ul>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Query the live Caddy config via the Admin API
curl -s localhost:2019/config/ | jq
# Find the route for a specific hostname
curl -s localhost:2019/config/ | jq '.. | .match? // empty | select(.host[]? | contains("media.lab"))'
# Tail Caddy logs for upstream errors
docker logs caddy --tail 50 -f`}</code></pre>
<h2>Container health</h2>
<p>
If a service shows <em>Unhealthy</em> or <em>Down</em> on the dashboard, the problem is the container itself.
Go straight to Docker.
</p>
<ul>
<li><strong>Check</strong>: <code>docker ps -a</code> is the container running, restarting, or exited?</li>
<li><strong>Check</strong>: <code>docker logs &lt;container&gt;</code> look for crash loops, missing files, bad config, or auth failures.</li>
<li><strong>Check</strong>: the container&apos;s healthcheck (if defined). DashCaddy surfaces container healthchecks in the UI; a failing healthcheck means the app is up but not ready (e.g. still migrating a database).</li>
<li><strong>Check</strong>: are volumes mounted and environment variables correct? Bad secrets (wrong DB password, missing API key) are the most common cause of immediate exits.</li>
</ul>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# List all containers including stopped ones
docker ps -a --filter "name=media"
# Tail recent logs
docker logs media --tail 100
# Inspect the healthcheck status and exit codes
docker inspect media --format '{{.State.Health.Status}} {{.State.ExitCode}}'
# Check resource usage if the container is OOM-killing
docker stats --no-stream media`}</code></pre>
<h2>Performance issues</h2>
<p>
If DashCaddy itself is slow or unresponsive, the cause is usually resource pressure on the host or an
overloaded dependency.
</p>
<ul>
<li><strong>Host resources</strong>: check CPU, memory, and disk with <code>htop</code>, <code>free -h</code>, and <code>df -h</code>. DashCaddy is lightweight, but a host running dozens of containers can starve it.</li>
<li><strong>Disk I/O</strong>: slow disks make Docker operations (deploy, inspect, logs) sluggish. Check <code>iostat -x 1</code> for high <code>%util</code>.</li>
<li><strong>Docker daemon load</strong>: a wedged Docker daemon slows every operation. <code>docker info</code> and <code>systemctl status docker</code> reveal daemon-level issues.</li>
<li><strong>DNS latency</strong>: if Technitium is overloaded or remote, every DNS operation in DashCaddy slows down. Check Technitium&apos;s own health and resource usage.</li>
<li><strong>Polling overhead</strong>: if you have many scripts polling the REST API, switch them to the WebSocket channel or Prometheus endpoint to reduce load.</li>
</ul>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Quick host health snapshot
free -h && df -h | grep -E "^/dev|Filesystem"
docker stats --no-stream
uptime`}</code></pre>
<h2>Common error messages</h2>
<p>
The table maps the most frequently seen errors to their likely cause and fix. For the full catalog of
structured error codes across all modules, see the <a href="/docs/api">API guide</a>.
</p>
<table>
<thead>
<tr>
<th>Error</th>
<th>Likely cause</th>
<th>Fix</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>NET::ERR_CERT_AUTHORITY_INVALID</code></td>
<td>Client does not trust the DashCA root certificate</td>
<td>Install the root CA from the DashCA page on the client device</td>
</tr>
<tr>
<td><code>502 Bad Gateway</code></td>
<td>Caddy route points at a wrong/unreachable upstream port</td>
<td>Check the Caddyfile-as-Code view; fix the upstream host:port; re-apply</td>
</tr>
<tr>
<td><code>504 Gateway Timeout</code></td>
<td>Upstream is up but too slow to respond within the proxy timeout</td>
<td>Inspect container logs; increase Caddy proxy timeout if the app legitimately needs more time</td>
</tr>
<tr>
<td>Hostname does not resolve</td>
<td>Client is not using Technitium as its resolver, or the record was not created</td>
<td>Point client DNS at Technitium; verify the record exists; re-run DNS step</td>
</tr>
<tr>
<td><code>DNS_TOKEN_INVALID</code></td>
<td>Technitium API token expired or revoked</td>
<td>Regenerate the token in Technitium; update it under Settings DNS</td>
</tr>
<tr>
<td><code>PROXY_CADDY_UNREACHABLE</code></td>
<td>Caddy Admin API (localhost:2019) is down or firewalled</td>
<td>Restart Caddy; confirm the Admin API port is open to DashCaddy</td>
</tr>
<tr>
<td><code>DEPLOY_PORT_CONFLICT</code></td>
<td>Another container already holds the requested host port</td>
<td>Stop the conflicting container or choose a different port</td>
</tr>
<tr>
<td><code>LICENSE_EXPIRED</code></td>
<td>Premium license expired past the 7-day grace period</td>
<td>Renew from Settings Licensing; free-tier features remain available</td>
</tr>
<tr>
<td><code>LICENSE_MACHINE_LIMIT</code></td>
<td>License already bound to another machine</td>
<td>Deactivate on the old host before activating on the new one</td>
</tr>
<tr>
<td><code>AUTH_PERMISSION_DENIED</code></td>
<td>User/API key lacks the RBAC role for the action</td>
<td>Assign the needed role in Settings Users</td>
</tr>
<tr>
<td>WebSocket updates stall</td>
<td>A reverse proxy or firewall is dropping the WS upgrade</td>
<td>Allow WebSocket upgrades on the DashCaddy route in Caddy/firewall</td>
</tr>
<tr>
<td><code>429 Too Many Requests</code></td>
<td>API client exceeded the per-token rate limit</td>
<td>Back off and retry after <code>Retry-After</code>; switch polling to WS/Prometheus</td>
</tr>
</tbody>
</table>
<h2>Debug mode</h2>
<p>
When the standard checks do not reveal the problem, enable debug logging for verbose output from every layer.
Set the <code>LOG_LEVEL</code> environment variable to <code>debug</code> and restart DashCaddy:
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Enable debug logging (docker run)
docker run -d \\
-e LOG_LEVEL=debug \\
-v /var/run/docker.sock:/var/run/docker.sock \\
-p 3000:3000 \\
ghcr.io/dashcaddy/dashcaddy:latest
# Or in docker-compose.yml
services:
dashcaddy:
environment:
- LOG_LEVEL=debug
# Then tail the logs
docker logs dashcaddy -f --tail 200`}</code></pre>
<p>
Debug mode emits detailed logs for Docker operations, Caddy Admin API calls, DNS requests, certificate
workflows, and the AI/MCP layer. Reproduce the problem while debug logging is on, then grep the logs for the
relevant module. Disable debug mode when done it is verbose and not recommended for long-term production use.
</p>
<h2>Support resources</h2>
<p>
If you have worked through the layers above and are still stuck, the following resources can help:
</p>
<ul>
<li><strong>Integrations guide</strong> <a href="/docs/integrations">Infrastructure Integrations</a> explains what each layer expects and how to configure it.</li>
<li><strong>API error reference</strong> the <a href="/docs/api">API and Automation</a> guide lists all 80 structured error codes across 12 modules.</li>
<li><strong>Installation</strong> <a href="/docs/installation">Installation Guide</a> covers first-run setup and the Smart Defaults Wizard.</li>
<li><strong>Premium / licensing</strong> <a href="/docs/premium">Premium Features</a> covers license validation, grace periods, and machine binding.</li>
<li><strong>Priority support</strong> Premium license holders get priority support. Open a ticket from Settings Support in the dashboard.</li>
</ul>
<h2>Mindset</h2>
<p>
Most DashCaddy problems are really one dependency layer failing while the others are healthy. Use the health
endpoints to localize, then walk the debug order from the container outward. Fixing the right layer first is
always faster than reloading the whole stack. When in doubt, enable debug mode, reproduce the issue, and read
the logs for the failing module the answer is almost always there.
</p>
</DocsLayout>
<Footer />
</div>
);
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

+247 -126
View File
@@ -16,79 +16,119 @@ interface FeatureSection {
title: string;
description: string;
icon: string;
badge?: string;
features: FeatureItem[];
}
export default function FeaturesPage() {
const features: FeatureSection[] = [
{
id: 'deployment',
id: 'ai-self-hosting',
title: 'AI-Powered Self-Hosting',
description:
'Talk to your homelab. DashCaddy\'s AI Intent Router understands natural language commands, and the built-in MCP Server lets AI assistants like Claude and GPT control your infrastructure directly. The first self-hosting platform built for the agentic era.',
icon: '🤖',
badge: 'New',
features: [
{ icon: '🎯', label: 'AI Intent Router — natural language commands' },
{ icon: '🔌', label: 'MCP Server for Claude & GPT control' },
{ icon: '💬', label: '"deploy Plex", "check SSL status", "restart jellyfin"' },
{ icon: '🧠', label: 'Context-aware infrastructure reasoning' },
],
},
{
id: 'app-deployment',
title: 'App Deployment',
description: 'Deploy your favorite applications instantly from our library of 50+ pre-configured Docker templates. No manual configuration needed—just click, deploy, and go live.',
description:
'Deploy from a library of 92+ pre-configured Docker templates with one click. Automatic configuration, smart defaults, and service discovery that auto-detects running containers and lets you adopt them instantly. From zero to live in seconds.',
icon: '🚀',
features: [
{ icon: '⚡', label: 'One-click deployment' },
{ icon: '📦', label: '50+ app templates' },
{ icon: '⚙️', label: 'Auto-configuration' },
{ icon: '📦', label: '92+ app templates' },
{ icon: '⚙️', label: 'Automatic configuration' },
{ icon: '🔍', label: 'Service Discovery & auto-adopt' },
{ icon: '🧙', label: 'Smart Defaults Wizard' },
],
},
{
id: 'ssl-security',
title: 'SSL & Security',
description: 'Enterprise-grade security built in. Automatic SSL certificates, TOTP 2FA, encrypted credentials, and comprehensive audit logs to track every action.',
description:
'Enterprise-grade security, baked in. Automatic SSL via Caddy\'s internal CA, TOTP 2FA with email magic link, multi-user admin with role-based access, encrypted credential vault, comprehensive audit logs, and a Security Center that unifies events from every source.',
icon: '🔒',
features: [
{ icon: '🔐', label: 'Automatic SSL certificates' },
{ icon: '📱', label: 'TOTP 2FA authentication' },
{ icon: '🔑', label: 'Encrypted credentials' },
{ icon: '📝', label: 'Audit logs' },
{ icon: '📱', label: 'TOTP 2FA + email magic link' },
{ icon: '👥', label: 'Multi-user admin (RBAC + invites)' },
{ icon: '🔑', label: 'Encrypted credential vault' },
{ icon: '📝', label: 'Audit logging' },
{ icon: '🛡️', label: 'Security Center (multi-source events)' },
],
},
{
id: 'dns',
title: 'DNS Management',
description: 'Manage all your app subdomains from one dashboard. Automatic DNS record creation with Technitium DNS integration makes domain management effortless.',
id: 'dns-networking',
title: 'DNS & Networking',
description:
'Full DNS automation powered by Technitium DNS, plus a Caddyfile-as-code visual builder for crafting reverse proxy rules without touching config files. Manage subdomains, routes, and proxies from a single dashboard.',
icon: '🌐',
features: [
{ icon: '✨', label: 'Automatic DNS records' },
{ icon: '🔗', label: 'Technitium integration' },
{ icon: '📋', label: 'Subdomain management' },
{ icon: '🎯', label: 'Zero DNS config' },
{ icon: '🔗', label: 'Technitium DNS integration' },
{ icon: '🏗️', label: 'Caddyfile-as-code visual builder' },
{ icon: '🔁', label: 'Reverse proxy management' },
],
},
{
id: 'monitoring',
title: 'Monitoring & Health',
description: 'Real-time visibility into your infrastructure. Monitor container health, response times, and resource usage with detailed metrics and alerts.',
id: 'monitoring-ops',
title: 'Monitoring & Operations',
description:
'Real-time visibility into every container and service. WebSocket live updates keep your dashboard in sync instantly, health checks catch problems early, and Prometheus metrics export feeds your existing observability stack.',
icon: '📊',
features: [
{ icon: '🟢', label: 'Real-time status' },
{ icon: '⏱️', label: 'Response time tracking' },
{ icon: '💾', label: 'Resource monitoring' },
{ icon: '📈', label: 'Performance metrics' },
{ icon: '🟢', label: 'Real-time health monitoring' },
{ icon: '', label: 'WebSocket live updates' },
{ icon: '📈', label: 'Prometheus metrics export' },
{ icon: '💓', label: 'Container health checks' },
{ icon: '⏱️', label: 'Response time & resource tracking' },
],
},
{
id: 'docker',
title: 'Docker Management',
description: 'Control your entire Docker environment visually. View, manage, and scale containers, access logs, and perform updates without touching the command line.',
icon: '🐳',
id: 'backup-recovery',
title: 'Backup & Recovery',
description:
'One-click backup and disaster recovery built for peace of mind. Full-system backups with checksum verification, point-in-time restore, and a disaster recovery workflow that gets you back online fast when it matters most.',
icon: '💾',
features: [
{ icon: '🎮', label: 'Container control' },
{ icon: '📜', label: 'Live logs access' },
{ icon: '🔄', label: 'Auto-updates' },
{ icon: '📊', label: 'Resource monitoring' },
{ icon: '🔙', label: 'One-click backup & restore' },
{ icon: '🆘', label: 'Disaster Recovery mode' },
{ icon: '', label: 'Full-system backup w/ checksums' },
{ icon: '⏮️', label: 'Point-in-time restore' },
],
},
{
id: 'developer-platform',
title: 'Developer Platform',
description:
'DashCaddy isn\'t just a UI—it\'s a platform. A full REST API with JSON endpoints, a plugin/extension system for custom workflows, and internationalization across 5 languages including full RTL support.',
icon: '🛠️',
features: [
{ icon: '🌐', label: 'REST API (JSON, MCP protocol)' },
{ icon: '🔌', label: 'Plugin / Extension system' },
{ icon: '🔄', label: 'Auto-update manager' },
{ icon: '🌍', label: 'i18n: EN/ES/FR/DE/AR (RTL)' },
],
},
{
id: 'premium',
title: 'Premium Features',
description: 'Advanced features for power users and production deployments. SSO integration, multi-container recipes, and Docker Swarm orchestration.',
description:
'Power features for production and multi-host deployments. Auto-Login SSO across your apps, multi-container Recipes, Docker Swarm orchestration, and Fleet Management to register remote instances with parallel health probes and multi-host deploy plans.',
icon: '⭐',
features: [
{ icon: '🔑', label: 'Auto-Login SSO', premium: true },
{ icon: '📚', label: 'Recipes (stack deployment)', premium: true },
{ icon: '🚀', label: 'Docker Swarm orchestration', premium: true },
{ icon: '📚', label: 'Recipes (multi-container stacks)', premium: true },
{ icon: '🐳', label: 'Docker Swarm orchestration', premium: true },
{ icon: '📡', label: 'Multi-Host Fleet Management', premium: true },
{ icon: '🚀', label: 'Priority support', premium: true },
],
},
@@ -105,11 +145,18 @@ export default function FeaturesPage() {
</div>
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8 text-center">
<div className="mb-6 inline-flex items-center gap-2 rounded-full border border-brand-500/30 bg-brand-500/10 px-4 py-1.5 text-sm font-medium text-brand-300">
<span className="relative flex h-2 w-2">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-brand-400 opacity-75" />
<span className="relative inline-flex h-2 w-2 rounded-full bg-brand-400" />
</span>
AI-native self-hosting platform
</div>
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold mb-6">
Powerful <span className="text-brand-400">Features</span> Built In
Every Tool You Need to <span className="text-brand-400">Self-Host</span>
</h1>
<p className="text-xl text-surface-300 max-w-2xl mx-auto">
Everything you need to manage Docker applications professionally. From deployment to monitoring, SSL to securityit's all included.
From AI-powered deployment to enterprise security, real-time monitoring, and multi-host fleet management DashCaddy brings the entire self-hosting stack under one roof.
</p>
</div>
</section>
@@ -119,12 +166,22 @@ export default function FeaturesPage() {
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="space-y-24">
{features.map((feature, idx) => (
<div key={feature.id} className={`grid grid-cols-1 lg:grid-cols-2 gap-12 items-center ${idx % 2 === 1 ? 'lg:flex-row-reverse' : ''}`}>
<div
key={feature.id}
className={`grid grid-cols-1 lg:grid-cols-2 gap-12 items-center ${idx % 2 === 1 ? 'lg:flex-row-reverse' : ''}`}
>
{/* Content Side */}
<div className={idx % 2 === 1 ? 'lg:order-2' : ''}>
<div className="mb-6 inline-flex rounded-lg bg-brand-500/10 p-4 text-brand-400">
<div className="text-3xl">{feature.icon}</div>
</div>
{feature.badge && (
<span className="mb-3 ml-2 inline-block align-middle text-xs px-2 py-1 bg-brand-500/20 text-brand-300 rounded-full font-semibold uppercase tracking-wide">
{feature.badge}
</span>
)}
<h2 className="text-3xl sm:text-4xl font-bold mb-4 text-surface-50">
{feature.title}
</h2>
@@ -144,7 +201,9 @@ export default function FeaturesPage() {
}`}
>
<span className="text-xl flex-shrink-0">{item.icon}</span>
<span className={`text-sm font-medium ${item.premium ? 'text-brand-300' : 'text-surface-300'}`}>
<span
className={`text-sm font-medium ${item.premium ? 'text-brand-300' : 'text-surface-300'}`}
>
{item.label}
{item.premium && (
<span className="ml-1 inline-block text-xs px-2 py-0.5 bg-brand-500/20 text-brand-300 rounded font-semibold">
@@ -164,7 +223,10 @@ export default function FeaturesPage() {
<div className="relative rounded-2xl border border-surface-700/50 bg-surface-800/50 backdrop-blur p-8">
<div className="space-y-4">
{feature.features.slice(0, 3).map((item, itemIdx) => (
<div key={itemIdx} className="flex items-center gap-3 p-3 bg-surface-900/50 rounded-lg border border-surface-700/30">
<div
key={itemIdx}
className="flex items-center gap-3 p-3 bg-surface-900/50 rounded-lg border border-surface-700/30"
>
<div className="w-2 h-2 rounded-full bg-brand-400" />
<span className="text-sm text-surface-300">{item.label}</span>
</div>
@@ -172,7 +234,10 @@ export default function FeaturesPage() {
</div>
<div className="mt-6 pt-6 border-t border-surface-700/30">
<div className="text-sm text-surface-500 text-center">
<span className="text-brand-400 font-semibold">{feature.features.length} features</span> included
<span className="text-brand-400 font-semibold">
{feature.features.length} features
</span>{' '}
included
</div>
</div>
</div>
@@ -184,112 +249,168 @@ export default function FeaturesPage() {
</div>
</section>
{/* Feature Comparison Grid */}
{/* AI Features Highlight */}
<section className="relative py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-surface-900 to-surface-950">
<div className="absolute inset-0 -z-10">
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-[40rem] h-96 bg-brand-500/10 rounded-full blur-3xl opacity-40" />
</div>
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="mb-16 text-center">
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-4">
What's Included in <span className="text-brand-400">Free</span>
<span className="rounded-full bg-brand-500/20 px-4 py-2 text-sm font-semibold text-brand-300">
The Agentic Era of Self-Hosting
</span>
<h2 className="mt-6 text-3xl sm:text-4xl lg:text-5xl font-bold mb-4">
Control Your Homelab With <span className="text-brand-400">Natural Language</span>
</h2>
<p className="text-lg text-surface-400">
Start with the free tier and upgrade anytime when you need advanced features.
<p className="text-lg text-surface-400 max-w-2xl mx-auto">
DashCaddy speaks AI natively. Type what you want, or let your favorite assistant do it.
</p>
</div>
{/* Feature Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<FeatureCard
icon="🚀"
title="One-Click Deployment"
description="Deploy from 50+ pre-configured templates with zero configuration."
/>
<FeatureCard
icon="🔒"
title="Automatic SSL"
description="Secure your apps with automatically renewed SSL certificates."
/>
<FeatureCard
icon="🌐"
title="DNS Management"
description="Automatic DNS record creation with Technitium integration."
/>
<FeatureCard
icon="📊"
title="Real-Time Monitoring"
description="Track container health, response times, and resource usage."
/>
<FeatureCard
icon="🐳"
title="Docker Control"
description="Manage containers, access logs, and updates from one dashboard."
/>
<FeatureCard
icon="🔐"
title="Security Built-In"
description="TOTP 2FA, encrypted credentials, and audit logging included."
/>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* AI Intent Router */}
<div className="group relative overflow-hidden rounded-2xl border border-surface-700/50 bg-surface-800/50 backdrop-blur p-8 transition-all duration-300 hover:border-brand-500/50 hover:shadow-lg hover:shadow-brand-500/10">
<div className="absolute inset-0 bg-gradient-to-br from-brand-500/0 to-brand-500/0 group-hover:from-brand-500/5 group-hover:to-brand-500/10 transition-all duration-300 pointer-events-none" />
<div className="relative z-10">
<div className="mb-4 inline-flex rounded-lg bg-brand-500/10 p-3 text-brand-400 group-hover:bg-brand-500/20 group-hover:text-brand-300 transition-all duration-300">
<div className="text-3xl">🎯</div>
</div>
<h3 className="mb-2 text-xl font-semibold text-surface-50">AI Intent Router</h3>
<p className="text-surface-300 text-sm leading-relaxed mb-6">
Skip the menus. Just type a command in plain English and DashCaddy figures out the right actions.
</p>
<div className="space-y-2">
{['"deploy Plex"', '"check SSL status"', '"restart jellyfin"', '"show failing containers"'].map(
(cmd, i) => (
<div
key={i}
className="flex items-center gap-3 rounded-lg bg-surface-900/60 border border-surface-700/30 px-3 py-2"
>
<span className="text-brand-400 font-mono text-xs select-none"></span>
<span className="text-sm text-surface-300 font-mono">{cmd}</span>
</div>
)
)}
</div>
</div>
</div>
{/* MCP Server */}
<div className="group relative overflow-hidden rounded-2xl border border-surface-700/50 bg-surface-800/50 backdrop-blur p-8 transition-all duration-300 hover:border-brand-500/50 hover:shadow-lg hover:shadow-brand-500/10">
<div className="absolute inset-0 bg-gradient-to-br from-brand-500/0 to-brand-500/0 group-hover:from-brand-500/5 group-hover:to-brand-500/10 transition-all duration-300 pointer-events-none" />
<div className="relative z-10">
<div className="mb-4 inline-flex rounded-lg bg-brand-500/10 p-3 text-brand-400 group-hover:bg-brand-500/20 group-hover:text-brand-300 transition-all duration-300">
<div className="text-3xl">🔌</div>
</div>
<h3 className="mb-2 text-xl font-semibold text-surface-50">MCP Server</h3>
<p className="text-surface-300 text-sm leading-relaxed mb-6">
DashCaddy runs as a Model Context Protocol server, so AI assistants can manage your homelab directly.
</p>
<div className="space-y-2">
{[
'Claude desktop & API',
'GPT / ChatGPT integrations',
'Any MCP-compatible client',
'Scoped, audited, secure access',
].map((item, i) => (
<div
key={i}
className="flex items-center gap-3 rounded-lg bg-surface-900/60 border border-surface-700/30 px-3 py-2"
>
<div className="w-2 h-2 rounded-full bg-brand-400" />
<span className="text-sm text-surface-300">{item}</span>
</div>
))}
</div>
</div>
</div>
</div>
</div>
</section>
{/* Premium Section */}
{/* Free vs Premium Comparison */}
<section className="relative py-16 sm:py-20 lg:py-24 bg-surface-950">
<div className="absolute inset-0 -z-10">
<div className="absolute top-1/2 right-0 w-96 h-96 bg-brand-600/10 rounded-full blur-3xl opacity-20" />
</div>
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="rounded-2xl border border-brand-500/30 bg-gradient-to-br from-brand-950/50 to-surface-900 p-12">
<div className="max-w-3xl">
<div className="mb-6 inline-block">
<span className="rounded-full bg-brand-500/20 px-4 py-2 text-sm font-semibold text-brand-300">
Premium Features
<div className="mb-16 text-center">
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-4">
Free vs <span className="text-brand-400">Premium</span>
</h2>
<p className="text-lg text-surface-400">
A powerful free tier for everyone. Premium unlocks multi-host, SSO, and more.
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Free Column */}
<div className="rounded-2xl border border-surface-700/50 bg-surface-800/40 backdrop-blur p-8">
<div className="mb-6">
<h3 className="text-2xl font-bold text-surface-50">Free</h3>
<p className="text-sm text-surface-400 mt-1">Everything to self-host a single node.</p>
</div>
<ul className="space-y-3">
{[
'92+ one-click app templates',
'Automatic SSL certificates',
'Technitium DNS automation',
'Reverse proxy + Caddyfile builder',
'Real-time monitoring & WebSocket updates',
'TOTP 2FA + multi-user admin',
'Encrypted credential vault',
'Audit logging',
'Security Center',
'One-click backup & restore',
'AI Intent Router + MCP Server',
'JavaScript SDK & REST API',
'Plugin / Extension system',
'i18n (5 languages, RTL)',
'Prometheus metrics export',
].map((item, i) => (
<li key={i} className="flex items-start gap-3">
<svg className="mt-0.5 h-5 w-5 flex-shrink-0 text-brand-400" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12.75l6 6 9-13.5" />
</svg>
<span className="text-sm text-surface-300">{item}</span>
</li>
))}
</ul>
</div>
{/* Premium Column */}
<div className="relative rounded-2xl border border-brand-500/40 bg-gradient-to-br from-brand-950/40 to-surface-900 p-8">
<div className="absolute -top-3 right-6">
<span className="rounded-full bg-brand-500 px-3 py-1 text-xs font-semibold text-white uppercase tracking-wide">
Premium
</span>
</div>
<h2 className="text-3xl sm:text-4xl font-bold mb-6 text-surface-50">
Level Up With <span className="text-brand-400">Premium</span>
</h2>
<p className="text-lg text-surface-300 mb-8">
Get advanced features designed for power users and production deployments. Auto-Login SSO, stack recipes, Docker Swarm orchestration, and priority support.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<div className="flex items-start gap-4 p-4 rounded-lg bg-surface-800/50 border border-surface-700/30">
<span className="text-2xl flex-shrink-0">🔑</span>
<div>
<h3 className="font-semibold text-surface-50 mb-1">Auto-Login SSO</h3>
<p className="text-sm text-surface-400">Deploy apps with automatic single sign-on integration.</p>
</div>
</div>
<div className="flex items-start gap-4 p-4 rounded-lg bg-surface-800/50 border border-surface-700/30">
<span className="text-2xl flex-shrink-0">📚</span>
<div>
<h3 className="font-semibold text-surface-50 mb-1">Recipes</h3>
<p className="text-sm text-surface-400">Deploy multi-container stacks with one click.</p>
</div>
</div>
<div className="flex items-start gap-4 p-4 rounded-lg bg-surface-800/50 border border-surface-700/30">
<span className="text-2xl flex-shrink-0">🚀</span>
<div>
<h3 className="font-semibold text-surface-50 mb-1">Docker Swarm</h3>
<p className="text-sm text-surface-400">Orchestrate multi-node clusters effortlessly.</p>
</div>
</div>
<div className="flex items-start gap-4 p-4 rounded-lg bg-surface-800/50 border border-surface-700/30">
<span className="text-2xl flex-shrink-0"></span>
<div>
<h3 className="font-semibold text-surface-50 mb-1">Priority Support</h3>
<p className="text-sm text-surface-400">Get faster responses from our support team.</p>
</div>
</div>
<div className="mb-6">
<h3 className="text-2xl font-bold text-surface-50">Premium</h3>
<p className="text-sm text-surface-400 mt-1">For production, multi-host, and teams.</p>
</div>
<p className="text-sm text-brand-300 mb-4 font-medium">
Everything in Free, plus:
</p>
<ul className="space-y-3">
{[
'Auto-Login SSO for deployed apps',
'Recipes — multi-container stack deployment',
'Docker Swarm orchestration',
'Multi-Host Fleet Management (remote instances, parallel health probes, multi-host deploy plans)',
'Disaster Recovery (full-system backup w/ checksums)',
'Priority support',
].map((item, i) => (
<li key={i} className="flex items-start gap-3">
<svg className="mt-0.5 h-5 w-5 flex-shrink-0 text-brand-400" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span className="text-sm text-surface-200">{item}</span>
</li>
))}
</ul>
<Link
href="/pricing"
className="inline-flex items-center gap-2 rounded-lg bg-brand-500 px-6 py-3 font-semibold text-white hover:bg-brand-600 transition-all duration-200 hover:shadow-lg hover:shadow-brand-500/30"
className="mt-8 inline-flex items-center gap-2 rounded-lg bg-brand-500 px-6 py-3 font-semibold text-white hover:bg-brand-600 transition-all duration-200 hover:shadow-lg hover:shadow-brand-500/30"
>
Explore Premium Plans
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
@@ -301,7 +422,7 @@ export default function FeaturesPage() {
</div>
</section>
{/* FAQ Section */}
{/* FAQ CTA Section */}
<section className="relative py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-surface-950 to-surface-900">
<div className="mx-auto max-w-3xl px-4 sm:px-6 lg:px-8">
<div className="mb-12 text-center">
+75 -13
View File
@@ -1,5 +1,9 @@
@import "tailwindcss";
/* ── Theme system: light is default, .dark activates dark mode ── */
/* Surface colors are remapped so bg-surface-950 = page background, */
/* text-surface-50 = main text — in BOTH themes. The scale flips. */
@theme inline {
--color-brand-50: #eef6ff;
--color-brand-100: #d9eaff;
@@ -13,22 +17,55 @@
--color-brand-900: #18378f;
--color-brand-950: #142357;
--color-surface-50: #f8fafc;
--color-surface-100: #f1f5f9;
--color-surface-200: #e2e8f0;
--color-surface-300: #cbd5e1;
--color-surface-400: #94a3b8;
--color-surface-500: #64748b;
--color-surface-600: #475569;
--color-surface-700: #334155;
--color-surface-800: #1e293b;
--color-surface-900: #0f172a;
--color-surface-950: #020617;
/* Surface scale — mapped to semantic vars that flip with theme */
--color-surface-50: var(--s-50);
--color-surface-100: var(--s-100);
--color-surface-200: var(--s-200);
--color-surface-300: var(--s-300);
--color-surface-400: var(--s-400);
--color-surface-500: var(--s-500);
--color-surface-600: var(--s-600);
--color-surface-700: var(--s-700);
--color-surface-800: var(--s-800);
--color-surface-900: var(--s-900);
--color-surface-950: var(--s-950);
--font-sans: "Inter", system-ui, -apple-system, sans-serif;
--font-mono: "JetBrains Mono", "Fira Code", monospace;
}
/* ── Light theme (default) ── */
:root {
--s-50: #020617; /* main text — near black */
--s-100: #0f172a; /* heading text */
--s-200: #1e293b; /* strong text */
--s-300: #475569; /* secondary text */
--s-400: #64748b; /* muted text */
--s-500: #94a3b8; /* very muted */
--s-600: #cbd5e1; /* faint text */
--s-700: #e2e8f0; /* borders */
--s-800: #f1f5f9; /* card backgrounds */
--s-900: #f8fafc; /* secondary page bg */
--s-950: #ffffff; /* page background — white */
color-scheme: light;
}
/* ── Dark theme ── */
.dark {
--s-50: #f8fafc; /* main text — near white */
--s-100: #f1f5f9; /* heading text */
--s-200: #e2e8f0; /* strong text */
--s-300: #cbd5e1; /* secondary text */
--s-400: #94a3b8; /* muted text */
--s-500: #64748b; /* very muted */
--s-600: #475569; /* faint text */
--s-700: #334155; /* borders */
--s-800: #1e293b; /* card backgrounds */
--s-900: #0f172a; /* secondary page bg */
--s-950: #020617; /* page background — near black */
color-scheme: dark;
}
html {
scroll-behavior: smooth;
-webkit-font-smoothing: antialiased;
@@ -39,6 +76,7 @@ body {
background-color: var(--color-surface-950);
color: var(--color-surface-100);
font-family: var(--font-sans);
transition: background-color 0.3s ease, color 0.3s ease;
}
::selection {
@@ -54,9 +92,18 @@ body {
}
.glass-card {
background: rgba(30, 41, 59, 0.5);
background: var(--glass-bg);
backdrop-filter: blur(16px);
border: 1px solid rgba(148, 163, 184, 0.1);
border: 1px solid var(--glass-border);
}
:root {
--glass-bg: rgba(241, 245, 249, 0.5);
--glass-border: rgba(0, 0, 0, 0.08);
}
.dark {
--glass-bg: rgba(30, 41, 59, 0.5);
--glass-border: rgba(148, 163, 184, 0.1);
}
.glow-border {
@@ -71,3 +118,18 @@ body {
transparent
);
}
/* Theme toggle button */
.theme-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 8px;
transition: background-color 0.2s, color 0.2s;
cursor: pointer;
}
.theme-toggle:hover {
background-color: var(--color-surface-800);
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

+20 -4
View File
@@ -4,7 +4,7 @@ import "./globals.css";
export const metadata: Metadata = {
title: "DashCaddy - Self-Hosting Made Beautiful",
description:
"Deploy 50+ Docker apps with one click. Automatic SSL, DNS, and reverse proxy configuration. The all-in-one self-hosting dashboard.",
"Deploy 92+ Docker apps with one click. AI-powered self-hosting with automatic SSL, DNS, reverse proxy, security center, and fleet management.",
keywords: [
"self-hosting",
"docker",
@@ -14,11 +14,12 @@ export const metadata: Metadata = {
"ssl",
"dns",
"homelab",
"AI self-hosting",
],
openGraph: {
title: "DashCaddy - Self-Hosting Made Beautiful",
description:
"Deploy 50+ Docker apps with one click. Automatic SSL, DNS, and reverse proxy configuration.",
"Deploy 92+ Docker apps with one click. AI-powered self-hosting with automatic SSL, DNS, and reverse proxy.",
url: "https://dashcaddy.net",
siteName: "DashCaddy",
type: "website",
@@ -27,17 +28,32 @@ export const metadata: Metadata = {
card: "summary_large_image",
title: "DashCaddy - Self-Hosting Made Beautiful",
description:
"Deploy 50+ Docker apps with one click. Automatic SSL, DNS, and reverse proxy.",
"Deploy 92+ Docker apps with one click. AI-powered self-hosting.",
},
};
// Inline script to set theme class BEFORE first paint — prevents FOUC
const themeScript = `
(function() {
try {
var stored = localStorage.getItem('theme');
var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
var theme = stored || (prefersDark ? 'dark' : 'light');
if (theme === 'dark') document.documentElement.classList.add('dark');
} catch(e) {}
})();
`;
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" className="h-full antialiased">
<html lang="en" className="h-full antialiased" suppressHydrationWarning>
<head>
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
</head>
<body className="min-h-full flex flex-col bg-surface-950 text-surface-100" style={{ fontFamily: "'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif" }}>
{children}
</body>
+206 -60
View File
@@ -27,13 +27,25 @@ export default function Home() {
{/* Left Content */}
<div className="flex flex-col justify-center space-y-8">
<div className="space-y-4">
{/* AI badge */}
<div className="inline-flex items-center gap-2 rounded-full border border-brand-500/30 bg-brand-500/10 px-4 py-1.5 text-sm font-medium text-brand-400 backdrop-blur-sm">
<span className="relative flex h-2 w-2">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-brand-400 opacity-75" />
<span className="relative inline-flex h-2 w-2 rounded-full bg-brand-400" />
</span>
AI-Powered Self-Hosting
</div>
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold tracking-tight">
<span className="text-brand-400">Self-Hosting</span>
<br />
<span>Made Beautiful</span>
<span>Made Intelligent</span>
</h1>
<p className="text-xl text-surface-300 max-w-lg leading-relaxed">
Deploy Docker apps with one click. Automatic SSL certificates, DNS management, and reverse proxy configurationeverything you need for self-hosted perfection.
Just say &ldquo;deploy Plex&rdquo; DashCaddy&rsquo;s AI Intent Router handles the rest.
Deploy Docker apps with one click, automatic SSL, DNS, and reverse proxy.
92+ templates, fleet management, and an MCP control plane for AI assistants.
Everything you need for self-hosted perfection.
</p>
</div>
@@ -43,20 +55,20 @@ export default function Home() {
href="/pricing"
className="inline-flex items-center justify-center gap-2 rounded-lg bg-brand-500 px-8 py-3 text-base font-semibold text-white hover:bg-brand-600 transition-all duration-200 hover:shadow-lg hover:shadow-brand-500/30 hover:scale-105"
>
<span>Get Started Free</span>
<span>View Plans</span>
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
</svg>
</Link>
<a
href="#"
<Link
href="/docs"
className="inline-flex items-center justify-center gap-2 rounded-lg border border-surface-700 bg-surface-800/50 px-8 py-3 text-base font-semibold text-surface-50 hover:border-brand-400 hover:bg-surface-800 transition-all duration-200 hover:text-brand-400"
>
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.6.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
<span>Read the Docs</span>
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25" />
</svg>
View on GitHub
</a>
</Link>
</div>
{/* Trust indicators */}
@@ -69,6 +81,10 @@ export default function Home() {
</div>
<p className="text-sm text-surface-400">Trusted by self-hosting enthusiasts</p>
</div>
<div className="flex items-center gap-3">
<div className="text-2xl">🌍</div>
<p className="text-sm text-surface-400">5 languages incl. Arabic (RTL)</p>
</div>
</div>
</div>
@@ -92,6 +108,18 @@ export default function Home() {
<span className="text-xs font-mono text-surface-500">dashcaddy.local</span>
</div>
{/* AI command bar */}
<div className="bg-brand-500/10 border border-brand-500/30 rounded-lg p-3 animate-fade-in">
<div className="flex items-center gap-2">
<span className="text-sm">🤖</span>
<span className="text-sm font-mono text-brand-300">&ldquo;deploy Plex&rdquo;</span>
</div>
<div className="mt-2 flex items-center gap-2 text-xs text-surface-400">
<span className="bg-green-500/20 text-green-400 px-2 py-0.5 rounded"> deployed</span>
<span>SSL + DNS + route configured</span>
</div>
</div>
{/* Content area with animated elements */}
<div className="space-y-3">
{/* Container item 1 */}
@@ -160,41 +188,56 @@ export default function Home() {
Powerful Features
</h2>
<p className="text-lg text-surface-400 max-w-2xl mx-auto">
Everything you need to manage your self-hosted Docker applications professionally.
AI-native self-hosting from one-click deploys to fleet management and a full MCP control plane.
</p>
</div>
{/* Feature Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<FeatureCard
icon="🤖"
title="AI Intent Router"
description="Natural language to structured actions. Just type &ldquo;deploy Plex&rdquo; and DashCaddy provisions the container, SSL, DNS, and reverse proxy automatically."
/>
<FeatureCard
icon="🧩"
title="MCP Server"
description="Model Context Protocol integration turns DashCaddy into an AI-native control plane. Let your AI assistant manage your entire self-hosted infrastructure."
/>
<FeatureCard
icon="🚀"
title="One-Click App Deployment"
description="Choose from 50+ pre-configured Docker app templates. Deploy your favorite applications instantly without manual configuration."
/>
<FeatureCard
icon="🔒"
title="Automatic SSL Certificates"
description="Caddy internal CA automatically generates and renews SSL certificates. Secure your apps with zero configuration overhead."
/>
<FeatureCard
icon="🌐"
title="DNS Integration"
description="Automatic DNS record creation with Technitium DNS. Manage all your app subdomains effortlessly from one dashboard."
/>
<FeatureCard
icon="📊"
title="Real-Time Monitoring"
description="Monitor container health, response times, and resource usage. Get instant insights into your application performance."
/>
<FeatureCard
icon="🐳"
title="Docker Management"
description="View, manage, and scale your containers. Access logs, manage volumes, and handle Docker operations visually."
title="92+ App Templates"
description="Choose from 92+ pre-configured Docker app templates. Deploy your favorite applications instantly without manual configuration."
/>
<FeatureCard
icon="🛡️"
title="Security First"
description="TOTP 2FA, audit logs, encrypted credentials, and role-based access control built in."
title="Security Center"
description="Unified event pipeline aggregates Docker, Caddy, DNS, Tailscale, audit log, and health checker events into one real-time security dashboard."
/>
<FeatureCard
icon="🖥️"
title="Multi-Host Fleet"
description="Register remote DashCaddy instances, run parallel health probes across your fleet, and execute multi-host deployment plans from one console."
/>
<FeatureCard
icon="🔍"
title="Service Discovery"
description="Auto-detect running containers and adopt them with one click — Caddy route and DNS record created automatically, zero manual config."
/>
<FeatureCard
icon="🔒"
title="Security & Auth"
description="TOTP 2FA, email magic links, multi-user with admin invites, audit logging, and encrypted credential management built in."
/>
<FeatureCard
icon="♻️"
title="Disaster Recovery"
description="Full-system backups with SHA-256 checksum verification and one-click restore. Your infrastructure is always recoverable."
/>
<FeatureCard
icon="🔌"
title="Plugin System"
description="Extend DashCaddy with custom service types, notification providers, workflow actions, and dashboard widgets. A fully extensible platform."
/>
</div>
</div>
@@ -230,10 +273,10 @@ export default function Home() {
{/* Content */}
<div className="text-center pt-4">
<h3 className="text-xl font-semibold text-surface-50 mb-2">
Install
Install &amp; Setup Wizard
</h3>
<p className="text-surface-400">
Clone the DashCaddy repository and run the setup script. Takes just a few minutes on your server.
Install DashCaddy, then answer one question &ldquo;What do you want to self-host?&rdquo; The Smart Defaults Wizard matches your hardware profile and configures everything automatically.
</p>
</div>
</div>
@@ -257,7 +300,7 @@ export default function Home() {
Deploy
</h3>
<p className="text-surface-400">
Click "Deploy" on any app template. SSL certificates and DNS records are automatically configured.
Click &ldquo;Deploy&rdquo; on any template, or just ask the AI Intent Router in plain English. SSL certificates and DNS records are automatically configured.
</p>
</div>
</div>
@@ -277,7 +320,7 @@ export default function Home() {
Done
</h3>
<p className="text-surface-400">
Your app is live with SSL and DNS configured. Monitor health, manage containers, and backup from the dashboard.
Your app is live with SSL and DNS configured. Monitor health in real time, manage containers, and run one-click backups from the dashboard.
</p>
</div>
</div>
@@ -298,7 +341,7 @@ export default function Home() {
Why DashCaddy?
</h2>
<p className="text-lg text-surface-400 max-w-2xl mx-auto">
Compare the pain of manual setup versus the simplicity of DashCaddy.
Compare the pain of manual setup versus the power of an AI-native self-hosting platform.
</p>
</div>
@@ -323,7 +366,7 @@ export default function Home() {
<tr className="hover:bg-surface-800/30 transition-colors">
<td className="px-6 py-4 text-sm text-surface-300">SSL Certificates</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-surface-500">Manual configuration via Let's Encrypt</span>
<span className="text-surface-500">Manual configuration via Let&rsquo;s Encrypt</span>
</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-green-400 font-semibold"> Automatic</span>
@@ -335,7 +378,7 @@ export default function Home() {
<span className="text-surface-500">Manual DNS records</span>
</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-green-400 font-semibold">✓ Automatic</span>
<span className="text-green-400 font-semibold"> Automatic (Technitium)</span>
</td>
</tr>
<tr className="hover:bg-surface-800/30 transition-colors">
@@ -344,7 +387,7 @@ export default function Home() {
<span className="text-surface-500">Complex Nginx/Caddy config</span>
</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-green-400 font-semibold">✓ One-click</span>
<span className="text-green-400 font-semibold"> Caddyfile-as-Code builder</span>
</td>
</tr>
<tr className="hover:bg-surface-800/30 transition-colors">
@@ -353,7 +396,61 @@ export default function Home() {
<span className="text-surface-500">Write your own compose files</span>
</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-green-400 font-semibold">✓ 50+ pre-configured</span>
<span className="text-green-400 font-semibold"> 92+ pre-configured</span>
</td>
</tr>
<tr className="hover:bg-surface-800/30 transition-colors">
<td className="px-6 py-4 text-sm text-surface-300">AI Commands</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-surface-500">Not available</span>
</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-green-400 font-semibold"> AI Intent Router + MCP</span>
</td>
</tr>
<tr className="hover:bg-surface-800/30 transition-colors">
<td className="px-6 py-4 text-sm text-surface-300">Fleet Management</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-surface-500">SSH into each host manually</span>
</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-green-400 font-semibold"> Multi-host, parallel probes</span>
</td>
</tr>
<tr className="hover:bg-surface-800/30 transition-colors">
<td className="px-6 py-4 text-sm text-surface-300">Security Center</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-surface-500">Scattered logs &amp; tools</span>
</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-green-400 font-semibold"> Unified event pipeline</span>
</td>
</tr>
<tr className="hover:bg-surface-800/30 transition-colors">
<td className="px-6 py-4 text-sm text-surface-300">Disaster Recovery</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-surface-500">Manual scripts, no verification</span>
</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-green-400 font-semibold"> SHA-256 verified backups</span>
</td>
</tr>
<tr className="hover:bg-surface-800/30 transition-colors">
<td className="px-6 py-4 text-sm text-surface-300">Real-Time Updates</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-surface-500">Manual refresh, CLI only</span>
</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-green-400 font-semibold"> WebSocket live dashboard</span>
</td>
</tr>
<tr className="hover:bg-surface-800/30 transition-colors">
<td className="px-6 py-4 text-sm text-surface-300">Plugin System</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-surface-500">Not extensible</span>
</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-green-400 font-semibold"> Custom widgets, providers</span>
</td>
</tr>
<tr className="hover:bg-surface-800/30 transition-colors">
@@ -362,11 +459,11 @@ export default function Home() {
<span className="text-surface-500">Third-party tools required</span>
</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-green-400 font-semibold">✓ Built-in</span>
<span className="text-green-400 font-semibold"> Built-in + Prometheus export</span>
</td>
</tr>
<tr className="hover:bg-surface-800/30 transition-colors">
<td className="px-6 py-4 text-sm text-surface-300">Backup & Restore</td>
<td className="px-6 py-4 text-sm text-surface-300">Backup &amp; Restore</td>
<td className="px-6 py-4 text-center text-sm">
<span className="text-surface-500">Manual scripts and planning</span>
</td>
@@ -398,7 +495,7 @@ export default function Home() {
Save Countless Hours
</h3>
<p className="text-surface-400 text-sm">
What takes days of manual configuration takes minutes with DashCaddy's automated setup and configuration.
What takes days of manual configuration takes minutes with DashCaddy&rsquo;s Smart Defaults Wizard and automated setup.
</p>
</div>
</div>
@@ -412,7 +509,7 @@ export default function Home() {
Enterprise Security
</h3>
<p className="text-surface-400 text-sm">
TOTP 2FA, encrypted credentials, audit logging, and role-based access control built in.
TOTP 2FA, magic links, encrypted credentials, audit logging, and a unified Security Center built in.
</p>
</div>
</div>
@@ -426,7 +523,7 @@ export default function Home() {
Scale Confidently
</h3>
<p className="text-surface-400 text-sm">
Real-time monitoring and health checks give you visibility into your infrastructure 24/7.
Real-time WebSocket monitoring, Prometheus metrics export, and multi-host fleet management give you visibility across your infrastructure 24/7.
</p>
</div>
</div>
@@ -440,7 +537,7 @@ export default function Home() {
Never Lose Data
</h3>
<p className="text-surface-400 text-sm">
One-click backup and restore functionality ensures your data is always protected and recoverable.
Disaster recovery with full-system backups, SHA-256 checksum verification, and one-click restore keeps your data protected.
</p>
</div>
</div>
@@ -449,6 +546,55 @@ export default function Home() {
</div>
</section>
{/* Developer & Integrations Section */}
<section id="integrations" className="relative py-16 sm:py-20 lg:py-24 bg-surface-950">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
{/* Section Header */}
<div className="mb-16 text-center">
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-4 text-surface-50">
Built for Developers
</h2>
<p className="text-lg text-surface-400 max-w-2xl mx-auto">
Everything you need to automate, integrate, and extend DashCaddy.
</p>
</div>
{/* Integration Grid */}
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
<div className="rounded-lg border border-surface-700/50 bg-surface-800/50 p-5 text-center hover:border-brand-500/50 transition-colors">
<div className="text-3xl mb-2">📦</div>
<h3 className="text-sm font-semibold text-surface-50">REST API</h3>
<p className="text-xs text-surface-400 mt-1">Full JSON API</p>
</div>
<div className="rounded-lg border border-surface-700/50 bg-surface-800/50 p-5 text-center hover:border-brand-500/50 transition-colors">
<div className="text-3xl mb-2">📊</div>
<h3 className="text-sm font-semibold text-surface-50">Prometheus</h3>
<p className="text-xs text-surface-400 mt-1">Metrics export</p>
</div>
<div className="rounded-lg border border-surface-700/50 bg-surface-800/50 p-5 text-center hover:border-brand-500/50 transition-colors">
<div className="text-3xl mb-2">🗂</div>
<h3 className="text-sm font-semibold text-surface-50">Caddyfile-as-Code</h3>
<p className="text-xs text-surface-400 mt-1">Visual proxy builder</p>
</div>
<div className="rounded-lg border border-surface-700/50 bg-surface-800/50 p-5 text-center hover:border-brand-500/50 transition-colors">
<div className="text-3xl mb-2">🌍</div>
<h3 className="text-sm font-semibold text-surface-50">i18n</h3>
<p className="text-xs text-surface-400 mt-1">5 languages, RTL</p>
</div>
<div className="rounded-lg border border-surface-700/50 bg-surface-800/50 p-5 text-center hover:border-brand-500/50 transition-colors">
<div className="text-3xl mb-2">🔌</div>
<h3 className="text-sm font-semibold text-surface-50">Plugin API</h3>
<p className="text-xs text-surface-400 mt-1">Extend everything</p>
</div>
<div className="rounded-lg border border-surface-700/50 bg-surface-800/50 p-5 text-center hover:border-brand-500/50 transition-colors">
<div className="text-3xl mb-2">🔐</div>
<h3 className="text-sm font-semibold text-surface-50">TypeScript</h3>
<p className="text-xs text-surface-400 mt-1">Full type definitions</p>
</div>
</div>
</div>
</section>
{/* Final CTA Section */}
<section id="get-started" className="relative py-20 sm:py-24 lg:py-32 bg-gradient-to-b from-surface-900 to-surface-950 overflow-hidden">
{/* Background effects */}
@@ -463,7 +609,7 @@ export default function Home() {
<span className="text-brand-400">Self-Hosting Setup?</span>
</h2>
<p className="text-xl text-surface-300 mb-12 max-w-2xl mx-auto leading-relaxed">
Join developers and self-hosting enthusiasts who've simplified their infrastructure management with DashCaddy.
Join developers and self-hosting enthusiasts who&rsquo;ve simplified their infrastructure management with DashCaddy&rsquo;s AI-native platform.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
@@ -471,34 +617,34 @@ export default function Home() {
href="/pricing"
className="inline-flex items-center justify-center gap-2 rounded-lg bg-brand-500 px-10 py-4 text-lg font-semibold text-white hover:bg-brand-600 transition-all duration-200 hover:shadow-lg hover:shadow-brand-500/30 hover:scale-105"
>
Start Free Today
Get Started
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
</svg>
</Link>
<a
href="#"
<Link
href="/docs"
className="inline-flex items-center justify-center gap-2 rounded-lg border border-surface-700 bg-surface-800/50 px-10 py-4 text-lg font-semibold text-surface-50 hover:border-brand-400 hover:bg-surface-800 transition-all duration-200 hover:text-brand-400"
>
View Documentation
</a>
</Link>
</div>
{/* Trust badges */}
<div className="mt-12 flex flex-wrap justify-center gap-8 text-center">
<div>
<div className="text-2xl font-bold text-brand-400">100%</div>
<p className="text-sm text-surface-400">Open Source</p>
</div>
<div className="border-l border-surface-700/50" />
<div>
<div className="text-2xl font-bold text-brand-400">Self-Hosted</div>
<p className="text-sm text-surface-400">Your Data, Your Rules</p>
</div>
<div className="border-l border-surface-700/50" />
<div>
<div className="text-2xl font-bold text-brand-400">AI-Native</div>
<p className="text-sm text-surface-400">Intent Router + MCP</p>
</div>
<div className="border-l border-surface-700/50" />
<div>
<div className="text-2xl font-bold text-brand-400">24/7</div>
<p className="text-sm text-surface-400">Community Support</p>
<p className="text-sm text-surface-400">Real-Time Monitoring</p>
</div>
</div>
</div>
+300 -280
View File
@@ -1,280 +1,300 @@
'use client';
import { useState } from 'react';
import Link from 'next/link';
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
export default function PricingPage() {
const [isAnnual, setIsAnnual] = useState(false);
const plans = [
{
name: 'Free',
price: '0',
period: 'forever',
description: 'Perfect for getting started with self-hosting',
features: [
'Dashboard & monitoring',
'Up to 10 services',
'50+ app templates',
'Automatic SSL & DNS',
'TOTP 2FA',
'Community support',
],
cta: {
text: 'Get Started Free',
href: '/docs',
},
highlighted: false,
},
{
name: 'Premium',
price: isAnnual ? '99' : '20',
period: isAnnual ? 'per year' : 'per month',
savings: isAnnual ? 'Save 58%' : null,
description: 'For power users and production deployments',
features: [
'Everything in Free, plus:',
'Unlimited services',
'Auto-Login SSO for deployed apps',
'Recipes (multi-container stack deployment)',
'Docker Swarm (multi-node cluster orchestration)',
'Priority email support',
'Early access to new features',
],
cta: {
text: 'Start 14-Day Free Trial',
href: '/api/checkout?plan=premium',
},
highlighted: true,
},
];
const faqs = [
{
question: 'Can I try Premium for free?',
answer: 'Yes! Premium includes a 14-day free trial. No credit card required. You can cancel anytime.',
},
{
question: 'What happens when my subscription ends?',
answer: 'Your subscription gracefully downgrades to the Free tier. All your data remains intact—no data loss. You can resubscribe at any time.',
},
{
question: 'Can I self-host the license server?',
answer: 'Coming soon! We\'re working on a self-hosted license server option for enterprise deployments.',
},
{
question: 'Do you offer refunds?',
answer: 'Yes, we offer a 30-day money-back guarantee. If you\'re not satisfied with Premium, contact support for a full refund.',
},
{
question: 'Is my data safe?',
answer: '100% self-hosted means your data never leaves your server. DashCaddy runs entirely on your infrastructure. We have no access to your applications, configurations, or data.',
},
];
const [expandedFaq, setExpandedFaq] = useState<number | null>(0);
return (
<div className="flex flex-col min-h-screen bg-surface-950 text-surface-50">
<Navbar />
{/* Hero Section */}
<section className="relative py-16 sm:py-20 lg:py-24">
<div className="absolute inset-0 -z-10">
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-96 h-96 bg-brand-500/20 rounded-full blur-3xl opacity-30 animate-pulse" />
</div>
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8 text-center">
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold mb-6">
Simple, Transparent <span className="text-brand-400">Pricing</span>
</h1>
<p className="text-xl text-surface-300 mb-8 max-w-2xl mx-auto">
Start free. Upgrade when you need advanced features. No surprises, no lock-in.
</p>
{/* Toggle for Monthly/Yearly */}
<div className="flex items-center justify-center gap-4 mb-12">
<span className={`text-sm font-medium ${!isAnnual ? 'text-surface-50' : 'text-surface-400'}`}>
Monthly
</span>
<button
onClick={() => setIsAnnual(!isAnnual)}
className={`relative inline-flex h-8 w-14 items-center rounded-full transition-colors ${
isAnnual ? 'bg-brand-500' : 'bg-surface-700'
}`}
aria-label="Toggle annual pricing"
>
<span
className={`inline-block h-6 w-6 transform rounded-full bg-white transition-transform ${
isAnnual ? 'translate-x-7' : 'translate-x-1'
}`}
/>
</button>
<span className={`text-sm font-medium ${isAnnual ? 'text-surface-50' : 'text-surface-400'}`}>
Annual
</span>
{isAnnual && (
<span className="ml-2 inline-block rounded-full bg-brand-500/20 px-3 py-1 text-sm font-semibold text-brand-300">
Best Value
</span>
)}
</div>
</div>
</section>
{/* Pricing Cards */}
<section className="relative py-12 sm:py-16 lg:py-20">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-5xl mx-auto">
{plans.map((plan, idx) => (
<div
key={idx}
className={`relative rounded-2xl border transition-all duration-300 ${
plan.highlighted
? 'border-brand-500/50 bg-gradient-to-br from-surface-800 to-surface-900 shadow-2xl shadow-brand-500/20 scale-105 md:scale-105'
: 'border-surface-700/50 bg-surface-800/50 hover:border-surface-700 hover:bg-surface-800/80'
}`}
>
{/* Popular Badge */}
{plan.highlighted && (
<div className="absolute -top-4 left-1/2 -translate-x-1/2">
<span className="inline-block rounded-full bg-brand-500 px-4 py-1 text-xs font-bold uppercase tracking-wide text-white">
Most Popular
</span>
</div>
)}
<div className="p-8 sm:p-10">
{/* Header */}
<div className="mb-8">
<h3 className="text-2xl font-bold text-surface-50 mb-2">{plan.name}</h3>
<p className="text-surface-400 text-sm mb-6">{plan.description}</p>
{/* Price */}
<div className="flex items-baseline gap-2 mb-2">
<span className="text-5xl font-bold text-surface-50">${plan.price}</span>
<span className="text-surface-400">/{plan.period}</span>
</div>
{plan.savings && (
<p className="text-sm text-brand-400 font-semibold">{plan.savings}</p>
)}
</div>
{/* CTA Button */}
<Link
href={plan.cta.href}
className={`block w-full rounded-lg px-6 py-3 text-center font-semibold transition-all duration-200 mb-8 ${
plan.highlighted
? 'bg-brand-500 text-white hover:bg-brand-600 hover:shadow-lg hover:shadow-brand-500/30'
: 'border border-surface-700 bg-surface-700/50 text-surface-50 hover:border-brand-400 hover:bg-surface-700 hover:text-brand-400'
}`}
>
{plan.cta.text}
</Link>
{/* Features List */}
<div className="border-t border-surface-700/50 pt-8">
<ul className="space-y-4">
{plan.features.map((feature, featureIdx) => (
<li key={featureIdx} className="flex items-start gap-3">
{feature.startsWith('Everything in') ? (
<span className="text-sm font-semibold text-surface-300">{feature}</span>
) : (
<>
<svg className="h-5 w-5 flex-shrink-0 text-green-400 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
<span className="text-surface-300 text-sm">{feature}</span>
</>
)}
</li>
))}
</ul>
</div>
</div>
</div>
))}
</div>
</div>
</section>
{/* FAQ Section */}
<section className="relative py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-surface-950 to-surface-900">
<div className="mx-auto max-w-3xl px-4 sm:px-6 lg:px-8">
<div className="mb-12 text-center">
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-4">
Frequently Asked <span className="text-brand-400">Questions</span>
</h2>
<p className="text-lg text-surface-400">
Have a question? We've got answers.
</p>
</div>
{/* FAQ Accordion */}
<div className="space-y-4">
{faqs.map((faq, idx) => (
<div
key={idx}
className="rounded-lg border border-surface-700/50 bg-surface-800/50 overflow-hidden transition-all duration-200 hover:border-surface-700"
>
<button
onClick={() => setExpandedFaq(expandedFaq === idx ? null : idx)}
className="w-full px-6 py-4 flex items-center justify-between hover:bg-surface-800/70 transition-colors"
>
<h3 className="text-lg font-semibold text-surface-50 text-left">{faq.question}</h3>
<svg
className={`h-6 w-6 flex-shrink-0 text-brand-400 transition-transform duration-200 ${
expandedFaq === idx ? 'rotate-180' : ''
}`}
fill="none"
viewBox="0 0 24 24"
strokeWidth={2}
stroke="currentColor"
>
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
</svg>
</button>
{expandedFaq === idx && (
<div className="border-t border-surface-700/50 bg-surface-900/50 px-6 py-4">
<p className="text-surface-300 leading-relaxed">{faq.answer}</p>
</div>
)}
</div>
))}
</div>
</div>
</section>
{/* Final CTA */}
<section className="relative py-16 sm:py-20 lg:py-24 bg-surface-950">
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8 text-center">
<h2 className="text-3xl sm:text-4xl font-bold mb-6">
Ready to Get Started?
</h2>
<p className="text-xl text-surface-300 mb-8 max-w-2xl mx-auto">
Try DashCaddy free forever or upgrade to Premium for advanced features.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Link
href="/docs"
className="inline-flex items-center justify-center gap-2 rounded-lg bg-brand-500 px-8 py-3 text-base font-semibold text-white hover:bg-brand-600 transition-all duration-200 hover:shadow-lg hover:shadow-brand-500/30"
>
View Documentation
</Link>
<Link
href="/"
className="inline-flex items-center justify-center gap-2 rounded-lg border border-surface-700 bg-surface-800/50 px-8 py-3 text-base font-semibold text-surface-50 hover:border-brand-400 hover:bg-surface-800 transition-colors"
>
Back to Home
</Link>
</div>
</div>
</section>
<Footer />
</div>
);
}
'use client';
import { useState } from 'react';
import Link from 'next/link';
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
// ─── Stripe Payment Links ───────────────────────────────────────────
// These are pre-generated checkout URLs from the Stripe Dashboard.
// Sami needs to provide his Stripe secret key so we can create these
// programmatically, or create them manually in the Stripe Dashboard.
// The product catalog (from src/billing/catalog.js):
// pro-30d → $20, 30-day license (one-time payment)
// pro-90d → $50, 90-day license (one-time payment)
// pro-180d → $70, 180-day license (one-time payment)
// pro-365d → $99, 365-day license (one-time payment)
// ─────────────────────────────────────────────────────────────────────
const STRIPE_LINKS: Record<string, string> = {
'30d': 'https://buy.stripe.com/7sY9AVgJm35P6Uo5j904800',
'90d': 'https://buy.stripe.com/bJe6oJ0Ko7m5emQ4f504801',
'180d': 'https://buy.stripe.com/4gMeVfct635P2E826X04802',
'365d': 'https://buy.stripe.com/8x228tct65dXdiM9zp04803',
};
export default function PricingPage() {
const [selectedPlan, setSelectedPlan] = useState<'30d' | '90d' | '180d' | '365d'>('365d');
const planOptions = [
{ key: '30d', label: '30 Days', price: 20, productId: 'pro-30d', perDay: '$0.67/day' },
{ key: '90d', label: '90 Days', price: 50, productId: 'pro-90d', perDay: '$0.56/day' },
{ key: '180d', label: '180 Days', price: 70, productId: 'pro-180d', perDay: '$0.39/day' },
{ key: '365d', label: '365 Days', price: 99, productId: 'pro-365d', perDay: '$0.27/day' },
] as const;
const selected = planOptions.find(p => p.key === selectedPlan)!;
const coreFeatures = [
'Dashboard & real-time service monitoring',
'92+ pre-configured app templates',
'Automatic SSL via Caddy internal CA',
'DNS automation via Technitium DNS',
'Reverse proxy management + Caddyfile-as-Code',
'AI Intent Router — natural language commands',
'MCP Server — AI assistant integration',
'Security Center — unified event pipeline',
'Service Discovery — auto-detect containers',
'Smart Defaults Wizard — guided setup',
'WebSocket real-time dashboard updates',
'TOTP 2FA + multi-user admin & invites',
'Audit logging & encrypted credential vault',
'One-click backup & disaster recovery',
'Plugin/extension system',
'REST API & MCP Server for AI assistants',
'Prometheus metrics export',
'i18n — 5 languages (EN/ES/FR/DE/AR)',
];
const premiumFeatures = [
'Everything in Core, plus:',
'Auto-Login SSO for deployed apps',
'Recipes — multi-container stack deployment',
'Swarm — Docker Swarm multi-node orchestration',
'Multi-Host Fleet Management — remote instance control',
'Priority support',
'One active machine per license',
'7-day grace period on expiry',
'One-time payment — no recurring billing',
];
const faqs = [
{
question: 'How does the license work?',
answer: 'You pay once and receive a license code valid for the selected duration (30, 90, 180, or 365 days). Paste it into your DashCaddy dashboard under Admin → License to unlock all premium features. No recurring billing — when it expires, you simply purchase again if you want to continue.',
},
{
question: 'What happens when my license expires?',
answer: 'Premium features gracefully deactivate after a 7-day grace period. Your services keep running — only the premium-gated features (SSO, Recipes, Swarm, Fleet Management) become unavailable. Purchase a new license at any time to re-enable them.',
},
{
question: 'Can I use DashCaddy without Premium?',
answer: 'Absolutely. The core platform is fully functional without a license — including AI commands, the Security Center, service discovery, monitoring, backup, and all 92+ app templates. Premium unlocks SSO, Recipes, Swarm orchestration, and Fleet Management for advanced multi-node setups.',
},
{
question: 'How many machines can I activate?',
answer: 'One active machine at a time per license. You can deactivate and move to a new machine when needed.',
},
{
question: 'Is my data safe?',
answer: 'DashCaddy runs entirely on your infrastructure. Your data never leaves your server. We have no access to your applications, configurations, or data.',
},
{
question: 'What payment methods do you accept?',
answer: 'We use Stripe for secure payment processing. All major credit cards are accepted. Payments are processed through Stripe\'s encrypted checkout — we never see or store your card details.',
},
];
const [expandedFaq, setExpandedFaq] = useState<number | null>(0);
return (
<div className="flex flex-col min-h-screen bg-surface-950 text-surface-50">
<Navbar />
{/* Hero */}
<section className="relative py-16 sm:py-20 lg:py-24">
<div className="absolute inset-0 -z-10">
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-96 h-96 bg-brand-500/20 rounded-full blur-3xl opacity-30 animate-pulse" />
</div>
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8 text-center">
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold mb-6">
Simple, Transparent <span className="text-brand-400">Pricing</span>
</h1>
<p className="text-xl text-surface-300 mb-8 max-w-2xl mx-auto">
The core platform is completely free including AI commands, Security Center, and all 92+ app templates. Premium unlocks advanced orchestration with a one-time payment.
</p>
</div>
</section>
{/* Two-column: Core vs Premium */}
<section className="relative py-12 sm:py-16 lg:py-20">
<div className="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{/* Core Plan */}
<div className="relative rounded-2xl border border-surface-700/50 bg-surface-800/50 hover:border-surface-700 hover:bg-surface-800/80 transition-all">
<div className="p-8 sm:p-10">
<div className="mb-8">
<h3 className="text-2xl font-bold text-surface-50 mb-2">Core</h3>
<p className="text-surface-400 text-sm mb-6">Everything you need to deploy, manage, and secure self-hosted services with AI built in.</p>
<div className="flex items-baseline gap-2">
<span className="text-5xl font-bold text-surface-50">Free</span>
</div>
<p className="text-surface-500 text-sm mt-2">No license required, no credit card</p>
</div>
<Link
href="/docs/installation"
className="block w-full rounded-lg border border-surface-700 bg-surface-700/50 px-6 py-3 text-center font-semibold text-surface-50 hover:border-brand-400 hover:bg-surface-700 hover:text-brand-400 transition-all"
>
Read Install Guide
</Link>
<div className="border-t border-surface-700/50 pt-8 mt-8">
<p className="text-xs font-semibold uppercase tracking-wide text-surface-500 mb-4">Everything included no limits</p>
<ul className="space-y-3">
{coreFeatures.map((feature, i) => (
<li key={i} className="flex items-start gap-3">
<svg className="h-5 w-5 flex-shrink-0 text-green-400 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
<span className="text-surface-300 text-sm">{feature}</span>
</li>
))}
</ul>
</div>
</div>
</div>
{/* Premium Plan */}
<div className="relative rounded-2xl border border-brand-500/50 bg-gradient-to-br from-surface-800 to-surface-900 shadow-2xl shadow-brand-500/20">
<div className="absolute -top-4 left-1/2 -translate-x-1/2">
<span className="inline-block rounded-full bg-brand-500 px-4 py-1 text-xs font-bold uppercase tracking-wide text-white">
Premium
</span>
</div>
<div className="p-8 sm:p-10">
<div className="mb-8">
<h3 className="text-2xl font-bold text-surface-50 mb-2">Premium</h3>
<p className="text-surface-400 text-sm mb-6">Advanced orchestration, SSO, and fleet management. One-time payment no subscription.</p>
{/* Plan selector */}
<div className="mb-4">
<div className="grid grid-cols-2 gap-2">
{planOptions.map((opt) => (
<button
key={opt.key}
onClick={() => setSelectedPlan(opt.key)}
className={`rounded-lg px-3 py-2 text-sm font-medium transition-all ${
selectedPlan === opt.key
? 'bg-brand-500 text-white'
: 'bg-surface-700/50 text-surface-400 hover:bg-surface-700 hover:text-surface-300'
}`}
>
{opt.label}
</button>
))}
</div>
</div>
<div className="flex items-baseline gap-2">
<span className="text-5xl font-bold text-surface-50">${selected.price}</span>
<span className="text-surface-400 text-lg">one-time</span>
</div>
<p className="text-brand-400 text-sm font-semibold mt-1">{selected.perDay} · {selected.productId}</p>
</div>
{/* Subscribe button — Stripe Payment Link */}
<a
href={STRIPE_LINKS[selectedPlan]}
target="_blank"
rel="noopener noreferrer"
className="block w-full rounded-lg bg-brand-500 px-6 py-3 text-center font-semibold text-white hover:bg-brand-600 hover:shadow-lg hover:shadow-brand-500/30 transition-all"
>
Buy Premium License
</a>
<p className="text-center text-xs text-surface-500 mt-2">
🔒 Secure checkout via Stripe · One-time payment
</p>
<div className="border-t border-surface-700/50 pt-8 mt-8">
<p className="text-xs font-semibold uppercase tracking-wide text-surface-500 mb-4">Everything in Core, plus</p>
<ul className="space-y-3">
{premiumFeatures.map((feature, i) => (
<li key={i} className="flex items-start gap-3">
{feature.startsWith('Everything') ? (
<span className="text-sm font-semibold text-surface-300">{feature}</span>
) : (
<>
<svg className="h-5 w-5 flex-shrink-0 text-brand-400 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
<span className="text-surface-300 text-sm">{feature}</span>
</>
)}
</li>
))}
</ul>
</div>
</div>
</div>
</div>
</div>
</section>
{/* FAQ */}
<section className="relative py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-surface-950 to-surface-900">
<div className="mx-auto max-w-3xl px-4 sm:px-6 lg:px-8">
<div className="mb-12 text-center">
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-4">
Frequently Asked <span className="text-brand-400">Questions</span>
</h2>
</div>
<div className="space-y-4">
{faqs.map((faq, idx) => (
<div
key={idx}
className="rounded-lg border border-surface-700/50 bg-surface-800/50 overflow-hidden transition-all hover:border-surface-700"
>
<button
onClick={() => setExpandedFaq(expandedFaq === idx ? null : idx)}
className="w-full px-6 py-4 flex items-center justify-between hover:bg-surface-800/70 transition-colors"
>
<h3 className="text-lg font-semibold text-surface-50 text-left">{faq.question}</h3>
<svg
className={`h-6 w-6 flex-shrink-0 text-brand-400 transition-transform ${expandedFaq === idx ? 'rotate-180' : ''}`}
fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor"
>
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
</svg>
</button>
{expandedFaq === idx && (
<div className="border-t border-surface-700/50 bg-surface-900/50 px-6 py-4">
<p className="text-surface-300 leading-relaxed">{faq.answer}</p>
</div>
)}
</div>
))}
</div>
</div>
</section>
{/* CTA */}
<section className="relative py-16 sm:py-20 lg:py-24 bg-surface-950">
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8 text-center">
<h2 className="text-3xl sm:text-4xl font-bold mb-6">Ready to Get Started?</h2>
<p className="text-xl text-surface-300 mb-8 max-w-2xl mx-auto">
Install DashCaddy and start deploying services in minutes. The core platform is free forever.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Link
href="/docs/installation"
className="inline-flex items-center justify-center gap-2 rounded-lg bg-brand-500 px-8 py-3 text-base font-semibold text-white hover:bg-brand-600 transition-all hover:shadow-lg hover:shadow-brand-500/30"
>
Installation Guide
</Link>
<Link
href="/"
className="inline-flex items-center justify-center gap-2 rounded-lg border border-surface-700 bg-surface-800/50 px-8 py-3 text-base font-semibold text-surface-50 hover:border-brand-400 hover:bg-surface-800 transition-colors"
>
Back to Home
</Link>
</div>
</div>
</section>
<Footer />
</div>
);
}
+2 -2
View File
@@ -38,7 +38,7 @@ function SuccessContent() {
<p className="text-lg text-surface-300 mb-8">
Your subscription is active. Check your email for your license key
and setup instructions. Your 14-day free trial has started.
and setup instructions.
</p>
<div className="glass-card rounded-xl p-6 mb-8 text-left">
@@ -73,7 +73,7 @@ function SuccessContent() {
4
</span>
<span>
Enjoy SSO, Recipes, Docker Swarm, and all premium features!
Enjoy SSO, Recipes, Docker Swarm, Fleet Management, and all premium features!
</span>
</li>
</ol>
+254 -181
View File
@@ -1,181 +1,254 @@
'use client';
import { useState } from 'react';
interface App {
name: string;
icon: string;
category: string;
}
const apps: App[] = [
// Media
{ name: 'Plex', icon: '🎬', category: 'Media' },
{ name: 'Jellyfin', icon: '📽️', category: 'Media' },
{ name: 'Kaleidescape', icon: '🎞️', category: 'Media' },
{ name: 'Emby', icon: '🎥', category: 'Media' },
{ name: 'Subsonic', icon: '🎵', category: 'Media' },
{ name: 'Synology Photos', icon: '📸', category: 'Media' },
// Downloads
{ name: 'Sonarr', icon: '📺', category: 'Downloads' },
{ name: 'Radarr', icon: '🎬', category: 'Downloads' },
{ name: 'Lidarr', icon: '🎶', category: 'Downloads' },
{ name: 'qBittorrent', icon: '', category: 'Downloads' },
{ name: 'Transmission', icon: '📤', category: 'Downloads' },
{ name: 'SABnzbd', icon: '📥', category: 'Downloads' },
// Productivity
{ name: 'Nextcloud', icon: '☁️', category: 'Productivity' },
{ name: 'Vikunja', icon: '', category: 'Productivity' },
{ name: 'OpenProject', icon: '📋', category: 'Productivity' },
{ name: 'Plane', icon: '🚀', category: 'Productivity' },
{ name: 'Actual Budget', icon: '💰', category: 'Productivity' },
{ name: 'HedgeDoc', icon: '📝', category: 'Productivity' },
// Management
{ name: 'Portainer', icon: '🐋', category: 'Management' },
{ name: 'Homelabs', icon: '🏠', category: 'Management' },
{ name: 'Yacht', icon: '', category: 'Management' },
{ name: 'DockSTARTer', icon: '', category: 'Management' },
{ name: 'Unraid', icon: '📦', category: 'Management' },
{ name: 'TrueNAS', icon: '💾', category: 'Management' },
// Security
{ name: 'Vaultwarden', icon: '🔐', category: 'Security' },
{ name: 'Bitwarden', icon: '🗝️', category: 'Security' },
{ name: 'Keycloak', icon: '🔑', category: 'Security' },
{ name: 'Authelia', icon: '🛡️', category: 'Security' },
{ name: 'OpenVPN', icon: '🌐', category: 'Security' },
{ name: 'WireGuard', icon: '📡', category: 'Security' },
// Development
{ name: 'Gitea', icon: '🐙', category: 'Development' },
{ name: 'GitLab', icon: '🦊', category: 'Development' },
{ name: 'Forgejo', icon: '🔧', category: 'Development' },
{ name: 'Jenkins', icon: '🤖', category: 'Development' },
{ name: 'Drone', icon: '🚁', category: 'Development' },
{ name: 'Code Server', icon: '💻', category: 'Development' },
// Monitoring
{ name: 'Grafana', icon: '📊', category: 'Monitoring' },
{ name: 'Prometheus', icon: '⚙️', category: 'Monitoring' },
{ name: 'Uptime Kuma', icon: '📈', category: 'Monitoring' },
{ name: 'Netdata', icon: '🔍', category: 'Monitoring' },
{ name: 'New Relic', icon: '👁️', category: 'Monitoring' },
{ name: 'Kibana', icon: '📉', category: 'Monitoring' },
// Additional
{ name: 'Home Assistant', icon: '🏡', category: 'Smart Home' },
{ name: 'Node-RED', icon: '🔴', category: 'Smart Home' },
{ name: 'OpenHAB', icon: '⚙️', category: 'Smart Home' },
{ name: 'Immich', icon: '📷', category: 'Media' },
{ name: 'Calibre', icon: '📚', category: 'Productivity' },
{ name: 'Paperless', icon: '📄', category: 'Productivity' },
];
const categories = [
'All',
'Media',
'Downloads',
'Productivity',
'Management',
'Security',
'Development',
'Monitoring',
'Smart Home',
];
export default function AppShowcase() {
const [activeCategory, setActiveCategory] = useState('All');
const filteredApps =
activeCategory === 'All'
? apps
: apps.filter((app) => app.category === activeCategory);
return (
<section className="relative py-12 px-4 sm:px-6 lg:px-8 bg-gradient-to-b from-surface-950 to-surface-900">
<div className="mx-auto max-w-7xl">
{/* Header */}
<div className="mb-12 text-center">
<h2 className="text-3xl sm:text-4xl font-bold text-surface-50 mb-4">
50+ One-Click App Templates
</h2>
<p className="text-lg text-surface-400 max-w-2xl mx-auto">
Deploy your favorite apps instantly with pre-configured templates, automatic SSL certificates, and integrated DNS management.
</p>
</div>
{/* Category Filter */}
<div className="mb-10 flex flex-wrap justify-center gap-2">
{categories.map((category) => (
<button
key={category}
onClick={() => setActiveCategory(category)}
className={`px-4 py-2 rounded-full text-sm font-medium transition-all duration-200 ${
activeCategory === category
? 'bg-brand-500 text-white shadow-lg shadow-brand-500/30'
: 'bg-surface-800 text-surface-300 hover:bg-surface-700 hover:text-surface-200'
}`}
>
{category}
</button>
))}
</div>
{/* App Grid */}
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4">
{filteredApps.map((app) => (
<div
key={`${app.name}-${app.category}`}
className="group relative flex flex-col items-center justify-center rounded-lg border border-surface-700 bg-surface-800/50 backdrop-blur-sm p-4 transition-all duration-300 hover:border-brand-500/50 hover:bg-surface-800/80 hover:shadow-lg hover:shadow-brand-500/10 hover:-translate-y-1"
>
{/* Background gradient on hover */}
<div className="absolute inset-0 bg-gradient-to-br from-brand-500/0 to-brand-500/0 group-hover:from-brand-500/5 group-hover:to-brand-500/10 rounded-lg transition-all duration-300 pointer-events-none" />
<div className="relative z-10 flex flex-col items-center justify-center text-center">
{/* Icon */}
<div className="text-4xl mb-2 transition-transform duration-300 group-hover:scale-110">
{app.icon}
</div>
{/* App Name */}
<h3 className="text-sm font-semibold text-surface-50 line-clamp-2 group-hover:text-brand-400 transition-colors">
{app.name}
</h3>
{/* Category Tag */}
<span className="text-xs text-surface-500 mt-1 group-hover:text-brand-400/70">
{app.category}
</span>
</div>
</div>
))}
</div>
{/* Stats */}
<div className="mt-16 grid grid-cols-3 gap-4 sm:gap-8">
<div className="text-center">
<div className="text-3xl sm:text-4xl font-bold text-brand-400 mb-2">
50+
</div>
<p className="text-sm text-surface-400">App Templates</p>
</div>
<div className="text-center">
<div className="text-3xl sm:text-4xl font-bold text-brand-400 mb-2">
0
</div>
<p className="text-sm text-surface-400">Configuration</p>
</div>
<div className="text-center">
<div className="text-3xl sm:text-4xl font-bold text-brand-400 mb-2">
1-Click
</div>
<p className="text-sm text-surface-400">Deploy</p>
</div>
</div>
</div>
</section>
);
}
'use client';
import { useState } from 'react';
interface App {
name: string;
icon: string;
category: string;
}
// All apps below have real deployable templates in DashCaddy.
// Auto-generated from src/docker/app-templates.js (92 templates, Aug 2026).
const apps: App[] = [
// Media
{ name: 'Plex', icon: '🎬', category: 'Media' },
{ name: 'Jellyfin', icon: '🍿', category: 'Media' },
{ name: 'Emby', icon: '🎥', category: 'Media' },
{ name: 'Navidrome', icon: '🎶', category: 'Media' },
{ name: 'Airsonic Advanced', icon: '🎧', category: 'Media' },
{ name: 'Audiobookshelf', icon: '🎧', category: 'Media' },
{ name: 'Calibre-Web', icon: '📖', category: 'Media' },
{ name: 'Komga', icon: '📚', category: 'Media' },
{ name: 'Kavita', icon: '📖', category: 'Media' },
{ name: 'Subsonic', icon: '🎵', category: 'Media' },
// Media Management
{ name: 'Sonarr', icon: '📺', category: 'Media Mgmt' },
{ name: 'Radarr', icon: '🎭', category: 'Media Mgmt' },
{ name: 'Prowlarr', icon: '🔍', category: 'Media Mgmt' },
{ name: 'Lidarr', icon: '🎵', category: 'Media Mgmt' },
{ name: 'Readarr', icon: '📚', category: 'Media Mgmt' },
{ name: 'Bazarr', icon: '💬', category: 'Media Mgmt' },
{ name: 'Seerr', icon: '🎫', category: 'Media Mgmt' },
{ name: 'Tautulli', icon: '📊', category: 'Media Mgmt' },
// Downloads
{ name: 'qBittorrent', icon: '⬇️', category: 'Downloads' },
{ name: 'SABnzbd', icon: '📰', category: 'Downloads' },
{ name: 'NZBGet', icon: '📥', category: 'Downloads' },
{ name: 'Transmission', icon: '🌊', category: 'Downloads' },
{ name: 'JDownloader 2', icon: '⬇️', category: 'Downloads' },
// Productivity
{ name: 'Nextcloud', icon: '☁️', category: 'Productivity' },
{ name: 'BookStack', icon: '📖', category: 'Productivity' },
{ name: 'Outline', icon: '📝', category: 'Productivity' },
{ name: 'Standard Notes', icon: '🔒', category: 'Productivity' },
{ name: 'Actual Budget', icon: '💰', category: 'Productivity' },
{ name: 'Mealie', icon: '🍳', category: 'Productivity' },
{ name: 'Paperless-ngx', icon: '📚', category: 'Productivity' },
{ name: 'Trilium Notes', icon: '🗒️', category: 'Productivity' },
{ name: 'Excalidraw', icon: '🎨', category: 'Productivity' },
{ name: 'Vikunja', icon: '', category: 'Productivity' },
{ name: 'OpenProject', icon: '📋', category: 'Productivity' },
{ name: 'Plane', icon: '✈️', category: 'Productivity' },
{ name: 'HedgeDoc', icon: '📝', category: 'Productivity' },
// Photos
{ name: 'Immich', icon: '📸', category: 'Photos' },
{ name: 'PhotoPrism', icon: '🖼️', category: 'Photos' },
// Files
{ name: 'FileBrowser', icon: '📁', category: 'Files' },
{ name: 'Syncthing', icon: '🔄', category: 'Files' },
{ name: 'Sami Files', icon: '📂', category: 'Files' },
// Management
{ name: 'Portainer', icon: '🐳', category: 'Management' },
{ name: 'Watchtower', icon: '👓', category: 'Management' },
{ name: 'Yacht', icon: '', category: 'Management' },
// Security
{ name: 'Vaultwarden', icon: '🔑', category: 'Security' },
{ name: 'DashCA', icon: '🔐', category: 'Security' },
{ name: 'Authentik', icon: '🔐', category: 'Security' },
{ name: 'CrowdSec', icon: '🛡️', category: 'Security' },
{ name: 'Authelia', icon: '🛡️', category: 'Security' },
{ name: 'Keycloak', icon: '🔑', category: 'Security' },
{ name: 'OpenVPN', icon: '🔒', category: 'Security' },
{ name: 'Bitwarden', icon: '🔐', category: 'Security' },
// Development
{ name: 'VS Code Server', icon: '💻', category: 'Development' },
{ name: 'Gitea', icon: '🦊', category: 'Development' },
{ name: 'Jenkins', icon: '🔧', category: 'Development' },
{ name: 'Drone CI', icon: '🐝', category: 'Development' },
{ name: 'Forgejo', icon: '🔧', category: 'Development' },
{ name: 'GitLab', icon: '🦚', category: 'Development' },
// Monitoring
{ name: 'Grafana', icon: '📊', category: 'Monitoring' },
{ name: 'Uptime Kuma', icon: '📈', category: 'Monitoring' },
{ name: 'Speedtest Tracker', icon: '⚡', category: 'Monitoring' },
{ name: 'Dozzle', icon: '📜', category: 'Monitoring' },
{ name: 'Prometheus', icon: '🔥', category: 'Monitoring' },
{ name: 'Netdata', icon: '📈', category: 'Monitoring' },
{ name: 'Kibana', icon: '📉', category: 'Monitoring' },
// Networking
{ name: 'Pi-hole', icon: '🛡️', category: 'Networking' },
{ name: 'WireGuard VPN', icon: '🔒', category: 'Networking' },
// DNS
{ name: 'Technitium DNS Server', icon: '🌐', category: 'DNS' },
{ name: 'BIND9 DNS Server', icon: '🔧', category: 'DNS' },
{ name: 'PowerDNS', icon: '⚡', category: 'DNS' },
{ name: 'CoreDNS', icon: '☁️', category: 'DNS' },
// Communication
{ name: 'Docker Mailserver', icon: '📧', category: 'Communication' },
{ name: 'Roundcube', icon: '💌', category: 'Communication' },
{ name: 'Matrix Synapse', icon: '💬', category: 'Communication' },
{ name: 'Rocket.Chat', icon: '🚀', category: 'Communication' },
// Database
{ name: 'PostgreSQL', icon: '🐘', category: 'Database' },
{ name: 'Redis', icon: '🔴', category: 'Database' },
{ name: 'MongoDB', icon: '🍃', category: 'Database' },
{ name: 'Adminer', icon: '🗄️', category: 'Database' },
// Gaming
{ name: 'Minecraft Server', icon: '⛏️', category: 'Gaming' },
{ name: 'Valheim Server', icon: '⚔️', category: 'Gaming' },
// Home Automation
{ name: 'Home Assistant', icon: '🏠', category: 'Home Automation' },
{ name: 'Node-RED', icon: '🔴', category: 'Home Automation' },
{ name: 'OpenHAB', icon: '⚙️', category: 'Home Automation' },
// Utilities
{ name: 'Weather', icon: '🌤️', category: 'Utilities' },
{ name: 'Digital Clock', icon: '🕐', category: 'Utilities' },
{ name: 'Homepage', icon: '🏡', category: 'Utilities' },
{ name: 'Homarr', icon: '🎯', category: 'Utilities' },
{ name: 'Change Detection', icon: '👁️', category: 'Utilities' },
{ name: 'Whoami', icon: '🔍', category: 'Utilities' },
{ name: 'Stirling PDF', icon: '📄', category: 'Utilities' },
{ name: 'IT Tools', icon: '🧰', category: 'Utilities' },
];
const categories = [
'All',
'Media',
'Media Mgmt',
'Downloads',
'Productivity',
'Photos',
'Files',
'Management',
'Security',
'Development',
'Monitoring',
'Networking',
'DNS',
'Communication',
'Database',
'Gaming',
'Home Automation',
'Utilities',
];
export default function AppShowcase() {
const [activeCategory, setActiveCategory] = useState('All');
const filteredApps =
activeCategory === 'All'
? apps
: apps.filter((app) => app.category === activeCategory);
return (
<section className="relative py-12 px-4 sm:px-6 lg:px-8 bg-gradient-to-b from-surface-950 to-surface-900">
<div className="mx-auto max-w-7xl">
{/* Header */}
<div className="mb-12 text-center">
<h2 className="text-3xl sm:text-4xl font-bold text-surface-50 mb-4">
92+ One-Click App Templates
</h2>
<p className="text-lg text-surface-400 max-w-2xl mx-auto">
Deploy your favorite apps instantly with pre-configured templates, automatic SSL certificates, and integrated DNS management.
</p>
</div>
{/* Category Filter */}
<div className="mb-10 flex flex-wrap justify-center gap-2">
{categories.map((category) => (
<button
key={category}
onClick={() => setActiveCategory(category)}
className={`px-4 py-2 rounded-full text-sm font-medium transition-all duration-200 ${
activeCategory === category
? 'bg-brand-500 text-white shadow-lg shadow-brand-500/30'
: 'bg-surface-800 text-surface-300 hover:bg-surface-700 hover:text-surface-200'
}`}
>
{category}
</button>
))}
</div>
{/* App Grid */}
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4">
{filteredApps.map((app) => (
<div
key={`${app.name}-${app.category}`}
className="group relative flex flex-col items-center justify-center rounded-lg border border-surface-700 bg-surface-800/50 backdrop-blur-sm p-4 transition-all duration-300 hover:border-brand-500/50 hover:bg-surface-800/80 hover:shadow-lg hover:shadow-brand-500/10 hover:-translate-y-1"
>
{/* Background gradient on hover */}
<div className="absolute inset-0 bg-gradient-to-br from-brand-500/0 to-brand-500/0 group-hover:from-brand-500/5 group-hover:to-brand-500/10 rounded-lg transition-all duration-300 pointer-events-none" />
<div className="relative z-10 flex flex-col items-center justify-center text-center">
{/* Icon */}
<div className="text-4xl mb-2 transition-transform duration-300 group-hover:scale-110">
{app.icon}
</div>
{/* App Name */}
<h3 className="text-sm font-semibold text-surface-50 line-clamp-2 group-hover:text-brand-400 transition-colors">
{app.name}
</h3>
{/* Category Tag */}
<span className="text-xs text-surface-500 mt-1 group-hover:text-brand-400/70">
{app.category}
</span>
</div>
</div>
))}
</div>
{/* Stats */}
<div className="mt-16 grid grid-cols-3 gap-4 sm:gap-8">
<div className="text-center">
<div className="text-3xl sm:text-4xl font-bold text-brand-400 mb-2">
92+
</div>
<p className="text-sm text-surface-400">App Templates</p>
</div>
<div className="text-center">
<div className="text-3xl sm:text-4xl font-bold text-brand-400 mb-2">
0
</div>
<p className="text-sm text-surface-400">Configuration</p>
</div>
<div className="text-center">
<div className="text-3xl sm:text-4xl font-bold text-brand-400 mb-2">
1-Click
</div>
<p className="text-sm text-surface-400">Deploy</p>
</div>
</div>
</div>
</section>
);
}
+156 -139
View File
@@ -1,139 +1,156 @@
import Link from 'next/link';
import Image from 'next/image';
export default function Footer() {
const currentYear = new Date().getFullYear();
const footerSections = [
{
title: 'Product',
links: [
{ label: 'Features', href: '/features' },
{ label: 'Pricing', href: '/pricing' },
{ label: 'Documentation', href: '/docs' },
{ label: 'About', href: '/about' },
],
},
{
title: 'Resources',
links: [
{ label: 'Getting Started', href: '/docs' },
{ label: 'API Reference', href: '/docs#api' },
{ label: 'GitHub', href: 'https://git.dashcaddy.net/sami7777/dashcaddy' },
{ label: 'Community', href: '#' },
],
},
{
title: 'Support',
links: [
{ label: 'Contact', href: 'mailto:support@dashcaddy.net' },
{ label: 'Discord', href: '#' },
{ label: 'Privacy Policy', href: '#' },
{ label: 'Terms of Service', href: '#' },
],
},
];
const socialLinks = [
{
icon: (
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.6.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
</svg>
),
label: 'GitHub',
href: 'https://git.dashcaddy.net/sami7777/dashcaddy',
},
{
icon: (
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M20.317 4.37a19.791 19.791 0 00-4.885-1.515a.074.074 0 00-.079.037c-.211.375-.444.864-.607 1.25a18.27 18.27 0 00-5.487 0c-.163-.386-.395-.875-.607-1.25a.077.077 0 00-.079-.037A19.736 19.736 0 003.677 4.37a.07.07 0 00-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 00.031.057 19.9 19.9 0 005.993 3.03.078.078 0 00.084-.028c.462-.63.873-1.295 1.226-1.994a.076.076 0 00-.042-.106 13.107 13.107 0 01-1.872-.892.077.077 0 01-.008-.128 10.2 10.2 0 00.372-.294.075.075 0 01.078-.01c3.928 1.793 8.18 1.793 12.062 0a.075.075 0 01.079.009c.12.098.246.198.373.295a.077.077 0 01-.006.127 12.299 12.299 0 01-1.873.892.076.076 0 00-.041.107c.359.698.77 1.364 1.225 1.994a.077.077 0 00.084.028 19.839 19.839 0 006.002-3.03.076.076 0 00.032-.057c.534-4.506-.9-8.4-3.821-11.865a.055.055 0 00-.032-.027zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.948-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.948 2.419-2.157 2.419zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.948-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.948 2.419-2.157 2.419z" />
</svg>
),
label: 'Discord',
href: '#',
},
];
return (
<footer className="border-t border-surface-700/50 bg-surface-950">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-12">
<div className="grid grid-cols-1 md:grid-cols-4 gap-8 mb-8">
{/* Brand Section */}
<div>
<Link href="/" className="flex items-center gap-2 font-bold text-lg text-brand-400 hover:text-brand-300 transition-colors mb-4">
<span>DashCaddy</span>
</Link>
<p className="text-surface-400 text-sm leading-relaxed mb-4">
Self-hosted Docker dashboard with automatic SSL, DNS, and reverse proxy. Making self-hosting beautiful and effortless.
</p>
<div className="flex items-center gap-4">
{socialLinks.map((link) => (
<a
key={link.label}
href={link.href}
className="text-surface-400 hover:text-brand-400 transition-colors"
aria-label={link.label}
target="_blank"
rel="noopener noreferrer"
>
{link.icon}
</a>
))}
</div>
</div>
{/* Link Sections */}
{footerSections.map((section) => (
<div key={section.title}>
<h3 className="text-sm font-semibold text-surface-50 mb-4">
{section.title}
</h3>
<ul className="space-y-3">
{section.links.map((link) => (
<li key={link.label}>
<Link
href={link.href}
className="text-sm text-surface-400 hover:text-brand-400 transition-colors"
>
{link.label}
</Link>
</li>
))}
</ul>
</div>
))}
</div>
{/* Support Link */}
<div className="border-t border-surface-700/50 pt-8 mb-8">
<p className="text-sm text-surface-400">
Need help? Email us at{' '}
<a
href="mailto:support@dashcaddy.net"
className="text-brand-400 hover:text-brand-300 transition-colors font-medium"
>
support@dashcaddy.net
</a>
</p>
</div>
{/* Copyright with samiahmed7777 logo */}
<div className="border-t border-surface-700/50 pt-8">
<div className="flex flex-col sm:flex-row items-center justify-center gap-3">
<p className="text-sm text-surface-500">
&copy; {currentYear} DashCaddy. All rights reserved. A product by
</p>
<Image
src="/images/samiahmed7777-logo.png"
alt="samiahmed7777"
width={160}
height={32}
className="h-7 w-auto opacity-80 hover:opacity-100 transition-opacity"
/>
</div>
</div>
</div>
</footer>
);
}
'use client';
import Link from 'next/link';
import Image from 'next/image';
import { useState, useEffect } from 'react';
export default function Footer() {
const currentYear = new Date().getFullYear();
const [theme, setTheme] = useState<'light' | 'dark'>('dark');
useEffect(() => {
const stored = localStorage.getItem('theme') as 'light' | 'dark' | null;
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
setTheme(stored || (prefersDark ? 'dark' : 'light'));
document.documentElement.classList.toggle('dark', (stored || (prefersDark ? 'dark' : 'light')) === 'dark');
}, []);
const footerSections = [
{
title: 'Product',
links: [
{ label: 'Features', href: '/features' },
{ label: 'Pricing', href: '/pricing' },
{ label: 'Documentation', href: '/docs' },
{ label: 'About', href: '/about' },
],
},
{
title: 'Resources',
links: [
{ label: 'Getting Started', href: '/docs/installation' },
{ label: 'API Reference', href: '/docs/api' },
{ label: 'Community', href: '#' },
],
},
{
title: 'Support',
links: [
{ label: 'Contact', href: 'mailto:support@dashcaddy.net' },
{ label: 'Docs', href: '/docs' },
{ label: 'Privacy Policy', href: '#' },
{ label: 'Terms of Service', href: '/legal/terms' },
],
},
];
const socialLinks = [
{
icon: (
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M20.317 4.37a19.791 19.791 0 00-4.885-1.515a.074.074 0 00-.079.037c-.211.375-.444.864-.607 1.25a18.27 18.27 0 00-5.487 0c-.163-.386-.395-.875-.607-1.25a.077.077 0 00-.079-.037A19.736 19.736 0 003.677 4.37a.07.07 0 00-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 00.031.057 19.9 19.9 0 005.993 3.03.078.078 0 00.084-.028c.462-.63.873-1.295 1.226-1.994a.076.076 0 00-.042-.106 13.107 13.107 0 01-1.872-.892.077.077 0 01-.008-.128 10.2 10.2 0 00.372-.294.075.075 0 01.078-.01c3.928 1.793 8.18 1.793 12.062 0a.075.075 0 01.079.009c.12.098.246.198.373.295a.077.077 0 01-.006.127 12.299 12.299 0 01-1.873.892.076.076 0 00-.041.107c.359.698.77 1.364 1.225 1.994a.077.077 0 00.084.028 19.839 19.839 0 006.002-3.03.076.076 0 00.032-.057c.534-4.506-.9-8.4-3.821-11.865a.055.055 0 00-.032-.027zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.948-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.948 2.419-2.157 2.419zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.948-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.948 2.419-2.157 2.419z" />
</svg>
),
label: 'Contact',
href: 'mailto:support@dashcaddy.net',
},
];
return (
<footer className="border-t border-surface-700/50 bg-surface-950">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-12">
<div className="grid grid-cols-1 md:grid-cols-4 gap-8 mb-8">
{/* Brand Section */}
<div>
<Link href="/" className="flex items-center transition-opacity hover:opacity-80 mb-4">
<Image
src={theme === 'dark' ? '/images/brand-wide-light.png' : '/images/brand-wide-dark.png'}
alt="DashCaddy"
width={160}
height={53}
className="h-9 w-auto"
/>
</Link>
<p className="text-surface-400 text-sm leading-relaxed mb-4">
Self-hosted Docker dashboard with automatic SSL, DNS, and reverse proxy. Making self-hosting beautiful and effortless.
</p>
<div className="flex items-center gap-4">
{socialLinks.map((link: any) => (
<a
key={link.label}
href={link.href}
className="text-surface-400 hover:text-brand-400 transition-colors"
aria-label={link.label}
target="_blank"
rel="noopener noreferrer"
>
{link.icon}
</a>
))}
</div>
</div>
{/* Link Sections */}
{footerSections.map((section) => (
<div key={section.title}>
<h3 className="text-sm font-semibold text-surface-50 mb-4">
{section.title}
</h3>
<ul className="space-y-3">
{section.links.map((link) => (
<li key={link.label}>
<Link
href={link.href}
className="text-sm text-surface-400 hover:text-brand-400 transition-colors"
>
{link.label}
</Link>
</li>
))}
</ul>
</div>
))}
</div>
{/* Support Link */}
<div className="border-t border-surface-700/50 pt-8 mb-8">
<p className="text-sm text-surface-400">
Need help? Email us at{' '}
<a
href="mailto:support@dashcaddy.net"
className="text-brand-400 hover:text-brand-300 transition-colors font-medium"
>
support@dashcaddy.net
</a>
</p>
</div>
{/* Copyright Disclaimer */}
<div className="border-t border-surface-700/50 pt-8 mb-8">
<p className="text-xs text-surface-500 text-center max-w-3xl mx-auto leading-relaxed">
DashCaddy provides deployment and management tools only. Users are solely responsible for
complying with all applicable copyright and intellectual property laws. Always stream and
access content that you own or have legal rights to. DashCaddy does not condone or support
piracy of any kind.
</p>
</div>
{/* Copyright with samiahmed7777 logo */}
<div className="border-t border-surface-700/50 pt-8">
<div className="flex flex-col sm:flex-row items-center justify-center gap-3">
<p className="text-sm text-surface-500">
&copy; {currentYear} DashCaddy. All rights reserved. A product by
</p>
<Image
src="/images/samiahmed7777-logo.png"
alt="samiahmed7777"
width={160}
height={32}
className="h-7 w-auto opacity-80 hover:opacity-100 transition-opacity"
/>
</div>
</div>
</div>
</footer>
);
}
+153 -98
View File
@@ -1,98 +1,153 @@
'use client';
import Link from 'next/link';
import { useState } from 'react';
export default function Navbar() {
const [isOpen, setIsOpen] = useState(false);
const navLinks = [
{ href: '#features', label: 'Features' },
{ href: '#pricing', label: 'Pricing' },
{ href: '#docs', label: 'Docs' },
{ href: '#about', label: 'About' },
];
return (
<nav className="sticky top-0 z-50 border-b border-surface-700/50 bg-surface-950/95 backdrop-blur supports-[backdrop-filter]:bg-surface-950/75">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="flex h-16 items-center justify-between">
{/* Logo */}
<Link href="/" className="flex items-center gap-2 font-bold text-xl text-brand-400 hover:text-brand-300 transition-colors">
<span>🚀</span>
<span>DashCaddy</span>
</Link>
{/* Desktop Navigation */}
<div className="hidden md:flex items-center gap-8">
{navLinks.map((link) => (
<Link
key={link.href}
href={link.href}
className="text-surface-300 hover:text-brand-400 transition-colors text-sm font-medium"
>
{link.label}
</Link>
))}
</div>
{/* CTA Button (Desktop) */}
<div className="hidden md:block">
<Link
href="#get-started"
className="inline-flex items-center gap-2 rounded-lg bg-brand-500 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-600 transition-all duration-200 hover:shadow-lg hover:shadow-brand-500/30"
>
Get Started
</Link>
</div>
{/* Mobile Menu Button */}
<button
onClick={() => setIsOpen(!isOpen)}
className="md:hidden inline-flex items-center justify-center rounded-lg p-2 text-surface-400 hover:bg-surface-800 hover:text-surface-200 transition-colors"
aria-label="Toggle menu"
>
<svg
className={`h-6 w-6 transition-transform duration-300 ${isOpen ? 'rotate-90' : ''}`}
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
/>
</svg>
</button>
</div>
{/* Mobile Navigation */}
{isOpen && (
<div className="border-t border-surface-700/50 bg-surface-900/50 backdrop-blur md:hidden">
<div className="space-y-1 px-2 py-4">
{navLinks.map((link) => (
<Link
key={link.href}
href={link.href}
className="block rounded-lg px-3 py-2 text-base font-medium text-surface-300 hover:bg-surface-800 hover:text-brand-400 transition-colors"
onClick={() => setIsOpen(false)}
>
{link.label}
</Link>
))}
<Link
href="#get-started"
className="block rounded-lg bg-brand-500 px-3 py-2 text-base font-medium text-white hover:bg-brand-600 transition-colors mt-4"
onClick={() => setIsOpen(false)}
>
Get Started
</Link>
</div>
</div>
)}
</div>
</nav>
);
}
'use client';
import Link from 'next/link';
import Image from 'next/image';
import { useState, useEffect } from 'react';
export default function Navbar() {
const [isOpen, setIsOpen] = useState(false);
const [theme, setTheme] = useState<'light' | 'dark'>('dark');
// Initialize theme from localStorage or system preference
useEffect(() => {
const stored = localStorage.getItem('theme') as 'light' | 'dark' | null;
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const initial = stored || (prefersDark ? 'dark' : 'light');
setTheme(initial);
document.documentElement.classList.toggle('dark', initial === 'dark');
}, []);
const toggleTheme = () => {
const next = theme === 'dark' ? 'light' : 'dark';
setTheme(next);
document.documentElement.classList.toggle('dark', next === 'dark');
localStorage.setItem('theme', next);
};
const navLinks = [
{ href: '/features', label: 'Features' },
{ href: '/pricing', label: 'Pricing' },
{ href: '/docs', label: 'Docs' },
{ href: '/about', label: 'About' },
];
return (
<nav className="sticky top-0 z-50 border-b border-surface-700/50 bg-surface-950/95 backdrop-blur supports-[backdrop-filter]:bg-surface-950/75">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="flex h-16 items-center justify-between">
{/* Logo */}
<Link href="/" className="flex items-center transition-opacity hover:opacity-80">
<Image
src={theme === 'dark' ? '/images/brand-wide-light.png' : '/images/brand-wide-dark.png'}
alt="DashCaddy"
width={349}
height={151}
className="h-14 w-auto"
priority
/>
</Link>
{/* Desktop Navigation */}
<div className="hidden md:flex items-center gap-8">
{navLinks.map((link) => (
<Link
key={link.href}
href={link.href}
className="text-surface-300 hover:text-brand-400 transition-colors text-sm font-medium"
>
{link.label}
</Link>
))}
{/* Theme toggle */}
<button
onClick={toggleTheme}
className="theme-toggle text-surface-300 hover:text-brand-400"
aria-label="Toggle theme"
>
{theme === 'dark' ? (
/* Sun icon */
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z" />
</svg>
) : (
/* Moon icon */
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M21.752 15.002A9.72 9.72 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z" />
</svg>
)}
</button>
</div>
{/* CTA + Mobile toggle */}
<div className="flex items-center gap-2 md:hidden">
<button
onClick={toggleTheme}
className="theme-toggle text-surface-300"
aria-label="Toggle theme"
>
{theme === 'dark' ? (
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z" />
</svg>
) : (
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M21.752 15.002A9.72 9.72 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z" />
</svg>
)}
</button>
<button
onClick={() => setIsOpen(!isOpen)}
className="inline-flex items-center justify-center rounded-lg p-2 text-surface-400 hover:bg-surface-800 hover:text-surface-200 transition-colors"
aria-label="Toggle menu"
>
<svg
className={`h-6 w-6 transition-transform duration-300 ${isOpen ? 'rotate-90' : ''}`}
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
>
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
</svg>
</button>
</div>
{/* Desktop CTA */}
<div className="hidden md:block">
<Link
href="/pricing"
className="inline-flex items-center gap-2 rounded-lg bg-brand-500 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-600 transition-all duration-200 hover:shadow-lg hover:shadow-brand-500/30"
>
Get Started
</Link>
</div>
</div>
{/* Mobile Navigation */}
{isOpen && (
<div className="border-t border-surface-700/50 bg-surface-900/50 backdrop-blur md:hidden">
<div className="space-y-1 px-2 py-4">
{navLinks.map((link) => (
<Link
key={link.href}
href={link.href}
className="block rounded-lg px-3 py-2 text-base font-medium text-surface-300 hover:bg-surface-800 hover:text-brand-400 transition-colors"
onClick={() => setIsOpen(false)}
>
{link.label}
</Link>
))}
<Link
href="/pricing"
className="block rounded-lg bg-brand-500 px-3 py-2 text-base font-medium text-white hover:bg-brand-600 transition-colors mt-4"
onClick={() => setIsOpen(false)}
>
Get Started
</Link>
</div>
</div>
)}
</div>
</nav>
);
}
+64
View File
@@ -0,0 +1,64 @@
import Link from 'next/link';
const docsLinks = [
{ href: '/docs/overview', label: 'Product Overview' },
{ href: '/docs/installation', label: 'Installation Guide' },
{ href: '/docs/first-service', label: 'Deploy Your First Service' },
{ href: '/docs/integrations', label: 'Infrastructure Integrations' },
{ href: '/docs/premium', label: 'Premium Features' },
{ href: '/docs/api', label: 'API and Automation' },
{ href: '/docs/troubleshooting', label: 'Troubleshooting' },
];
export default function DocsLayout({
title,
intro,
children,
}: {
title: string;
intro: string;
children: React.ReactNode;
}) {
return (
<section className="relative py-12 sm:py-16 lg:py-20">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 gap-10 lg:grid-cols-[260px_minmax(0,1fr)]">
<aside className="h-fit rounded-2xl border border-surface-700/50 bg-surface-900/50 p-5 lg:sticky lg:top-24">
<div className="mb-4">
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-brand-400">Documentation</p>
<h2 className="mt-2 text-lg font-semibold text-surface-50">DashCaddy Docs</h2>
</div>
<nav className="space-y-2">
<Link
href="/docs"
className="block rounded-lg px-3 py-2 text-sm font-medium text-surface-300 transition-colors hover:bg-surface-800 hover:text-brand-400"
>
Docs Home
</Link>
{docsLinks.map((link) => (
<Link
key={link.href}
href={link.href}
className="block rounded-lg px-3 py-2 text-sm font-medium text-surface-300 transition-colors hover:bg-surface-800 hover:text-brand-400"
>
{link.label}
</Link>
))}
</nav>
</aside>
<article className="min-w-0 rounded-2xl border border-surface-700/50 bg-surface-900/40 p-6 sm:p-8 lg:p-10">
<header className="mb-10 border-b border-surface-700/50 pb-6">
<p className="mb-3 text-xs font-semibold uppercase tracking-[0.2em] text-brand-400">DashCaddy Documentation</p>
<h1 className="text-3xl font-bold text-surface-50 sm:text-4xl">{title}</h1>
<p className="mt-4 max-w-3xl text-base leading-7 text-surface-300 sm:text-lg">{intro}</p>
</header>
<div className="prose prose-invert max-w-none prose-headings:text-surface-50 prose-p:text-surface-300 prose-li:text-surface-300 prose-strong:text-surface-100">
{children}
</div>
</article>
</div>
</div>
</section>
);
}