Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9fbd4f7be3 | ||
|
|
e780e5aa60 | ||
|
|
00aa123f73 | ||
|
|
3792c9c22d | ||
|
|
fbe1a7c95c | ||
|
|
4f03b8cf47 | ||
|
|
bb69b96816 | ||
|
|
aedaf4b4b5 | ||
|
|
f5104578c9 |
@@ -1,30 +1,7 @@
|
|||||||
# Stripe Configuration
|
# DashCaddy website configuration
|
||||||
# Get your keys from https://dashboard.stripe.com/apikeys
|
NEXT_PUBLIC_APP_URL=https://dashcaddy.net
|
||||||
STRIPE_SECRET_KEY=sk_live_your_secret_key_here
|
NEXT_PUBLIC_LICENSE_SERVER_URL=https://licenses.dashcaddy.net
|
||||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_your_publishable_key_here
|
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_your_publishable_key_here
|
||||||
|
|
||||||
# Stripe Webhook Secret
|
# The website no longer owns Stripe secret or webhook handling.
|
||||||
# Get this from https://dashboard.stripe.com/webhooks after creating an endpoint
|
# Stripe Checkout sessions and webhook processing live in dashcaddy-license-server.
|
||||||
STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret_here
|
|
||||||
|
|
||||||
# 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_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
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
| `CLAUDE.md` | Project instructions for Claude |
|
| `CLAUDE.md` | Project instructions for Claude |
|
||||||
| `FILELIST.md` | This file |
|
| `FILELIST.md` | This file |
|
||||||
| `README.md` | Project overview and setup guide |
|
| `README.md` | Project overview and setup guide |
|
||||||
| `STRIPE_SETUP.md` | Step-by-step Stripe configuration guide |
|
| `STRIPE_SETUP.md` | Billing architecture and license-server setup guide |
|
||||||
| `eslint.config.mjs` | ESLint configuration |
|
| `eslint.config.mjs` | ESLint configuration |
|
||||||
| `next-env.d.ts` | Next.js TypeScript declarations |
|
| `next-env.d.ts` | Next.js TypeScript declarations |
|
||||||
| `next.config.ts` | Next.js configuration |
|
| `next.config.ts` | Next.js configuration |
|
||||||
@@ -53,8 +53,8 @@
|
|||||||
|
|
||||||
| File | Endpoint | Purpose |
|
| File | Endpoint | Purpose |
|
||||||
|------|----------|---------|
|
|------|----------|---------|
|
||||||
| `src/app/api/checkout/route.ts` | `POST /api/checkout` | Creates Stripe Checkout session with 14-day trial |
|
| `src/app/api/checkout/route.ts` | `POST /api/checkout` | Disabled compatibility stub; production checkout lives in dashcaddy-license-server |
|
||||||
| `src/app/api/webhooks/stripe/route.ts` | `POST /api/webhooks/stripe` | Handles Stripe subscription lifecycle events |
|
| `src/app/api/webhooks/stripe/route.ts` | `POST /api/webhooks/stripe` | Disabled compatibility stub; production webhook lives in dashcaddy-license-server |
|
||||||
|
|
||||||
## Components (`src/components/`)
|
## Components (`src/components/`)
|
||||||
|
|
||||||
|
|||||||
@@ -36,11 +36,11 @@ Open [http://localhost:3000](http://localhost:3000) to see the site.
|
|||||||
|
|
||||||
## Stripe Integration
|
## Stripe Integration
|
||||||
|
|
||||||
See [STRIPE_SETUP.md](./STRIPE_SETUP.md) for detailed setup instructions.
|
See [STRIPE_SETUP.md](./STRIPE_SETUP.md) for the current billing and license-server setup.
|
||||||
|
|
||||||
**API Routes:**
|
**API Routes:**
|
||||||
- `POST /api/checkout` - Creates Stripe Checkout session
|
- Website pricing uses the external DashCaddy license server for checkout initiation.
|
||||||
- `POST /api/webhooks/stripe` - Handles Stripe webhook events
|
- Website-local Stripe routes are disabled compatibility stubs and are not the production billing path.
|
||||||
|
|
||||||
## Logo Setup
|
## Logo Setup
|
||||||
|
|
||||||
|
|||||||
@@ -1,83 +1,65 @@
|
|||||||
# Stripe Setup Guide for DashCaddy.net
|
# DashCaddy Billing Setup
|
||||||
|
|
||||||
## 1. Create a Stripe Account
|
DashCaddy website no longer processes Stripe webhooks locally.
|
||||||
|
|
||||||
Go to [stripe.com](https://stripe.com) and create an account (or log in).
|
Current architecture:
|
||||||
|
- `dashcaddy.net` handles pricing UI and starts checkout by calling the external license server
|
||||||
|
- `dashcaddy-license-server` owns Stripe secret usage, webhook handling, subscription state, and license generation
|
||||||
|
- DashCaddy app instances validate/deactivate against the external license server
|
||||||
|
|
||||||
## 2. Create Your Product and Prices
|
## Required website environment
|
||||||
|
|
||||||
In the Stripe Dashboard:
|
|
||||||
|
|
||||||
1. Go to **Products** > **Add Product**
|
|
||||||
2. Name: `DashCaddy Premium`
|
|
||||||
3. Description: `Premium license for DashCaddy - Self-hosting dashboard`
|
|
||||||
4. Create two prices:
|
|
||||||
- **Monthly**: $20.00 USD / month (recurring)
|
|
||||||
- **Yearly**: $99.00 USD / year (recurring)
|
|
||||||
5. Note down both **Price IDs** (they look like `price_1234...`)
|
|
||||||
|
|
||||||
## 3. Get Your API Keys
|
|
||||||
|
|
||||||
1. Go to **Developers** > **API Keys**
|
|
||||||
2. Copy your **Publishable key** (`pk_test_...` or `pk_live_...`)
|
|
||||||
3. Copy your **Secret key** (`sk_test_...` or `sk_live_...`)
|
|
||||||
|
|
||||||
## 4. Set Up Webhooks
|
|
||||||
|
|
||||||
1. Go to **Developers** > **Webhooks**
|
|
||||||
2. Click **Add endpoint**
|
|
||||||
3. URL: `https://dashcaddy.net/api/webhooks/stripe`
|
|
||||||
4. Select these events:
|
|
||||||
- `checkout.session.completed`
|
|
||||||
- `customer.subscription.updated`
|
|
||||||
- `customer.subscription.deleted`
|
|
||||||
- `invoice.payment_failed`
|
|
||||||
5. Copy the **Webhook signing secret** (`whsec_...`)
|
|
||||||
|
|
||||||
## 5. Configure Environment Variables
|
|
||||||
|
|
||||||
Copy `.env.example` to `.env.local` and fill in your values:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp .env.example .env.local
|
|
||||||
```
|
|
||||||
|
|
||||||
Edit `.env.local`:
|
|
||||||
|
|
||||||
```env
|
```env
|
||||||
STRIPE_SECRET_KEY=sk_live_your_actual_secret_key
|
|
||||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_your_actual_publishable_key
|
|
||||||
STRIPE_WEBHOOK_SECRET=whsec_your_actual_webhook_secret
|
|
||||||
STRIPE_PRICE_MONTHLY=price_your_monthly_price_id
|
|
||||||
STRIPE_PRICE_YEARLY=price_your_yearly_price_id
|
|
||||||
NEXT_PUBLIC_APP_URL=https://dashcaddy.net
|
NEXT_PUBLIC_APP_URL=https://dashcaddy.net
|
||||||
|
NEXT_PUBLIC_LICENSE_SERVER_URL=https://licenses.dashcaddy.net
|
||||||
|
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_your_publishable_key_here
|
||||||
```
|
```
|
||||||
|
|
||||||
## 6. Test with Stripe CLI (Optional)
|
## Required license server environment
|
||||||
|
|
||||||
For local development, use Stripe CLI to forward webhooks:
|
```env
|
||||||
|
PORT=3010
|
||||||
```bash
|
APP_BASE_URL=https://licenses.dashcaddy.net
|
||||||
stripe listen --forward-to localhost:3000/api/webhooks/stripe
|
DASHCADDY_WEBSITE_URL=https://dashcaddy.net
|
||||||
|
STRIPE_SECRET_KEY=sk_live_...
|
||||||
|
STRIPE_PUBLISHABLE_KEY=pk_live_...
|
||||||
|
STRIPE_WEBHOOK_SECRET=whsec_...
|
||||||
```
|
```
|
||||||
|
|
||||||
Use test card `4242 4242 4242 4242` with any future date and any CVC.
|
## Stripe product model
|
||||||
|
|
||||||
## 7. License Key Delivery
|
One Premium tier, four subscription cadences:
|
||||||
|
- 1 month — $25
|
||||||
|
- 3 months — $50
|
||||||
|
- 6 months — $65
|
||||||
|
- 12 months — $99
|
||||||
|
|
||||||
The webhook handler at `src/app/api/webhooks/stripe/route.ts` has TODO comments
|
No free trial.
|
||||||
where you need to implement:
|
7-day grace period.
|
||||||
|
Cancel at period end.
|
||||||
|
One active machine at a time.
|
||||||
|
Premium features only: `sso`, `recipes`, `swarm`.
|
||||||
|
|
||||||
1. **Generate license key** using the same format as DashCaddy's license-keygen
|
## Stripe webhook target
|
||||||
(DC-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX)
|
|
||||||
2. **Store it** in a database (Stripe metadata can also hold it)
|
|
||||||
3. **Email it** to the customer (use Stripe's receipt email or a service like
|
|
||||||
SendGrid/Resend)
|
|
||||||
4. **Link it** to the Stripe subscription ID so you can manage renewals/cancellations
|
|
||||||
|
|
||||||
## Pricing Strategy Notes
|
Configure Stripe to send events to the license server, not the website.
|
||||||
|
|
||||||
- **Monthly ($20/mo)**: Positioned as the flexibility option
|
Recommended webhook endpoint:
|
||||||
- **Yearly ($99/yr)**: ~$8.25/mo — 58% savings, this will be the primary seller
|
|
||||||
- **14-day free trial**: Enabled on both plans via `trial_period_days: 14`
|
```text
|
||||||
- **Promotion codes**: Enabled via `allow_promotion_codes: true`
|
https://licenses.dashcaddy.net/api/stripe/webhook
|
||||||
|
```
|
||||||
|
|
||||||
|
Recommended events:
|
||||||
|
- `checkout.session.completed`
|
||||||
|
- `customer.subscription.created`
|
||||||
|
- `customer.subscription.updated`
|
||||||
|
- `customer.subscription.deleted`
|
||||||
|
- `invoice.payment_failed`
|
||||||
|
|
||||||
|
## Website behavior
|
||||||
|
|
||||||
|
The website pricing page should start checkout via:
|
||||||
|
- `POST /api/checkout/session` on the external license server
|
||||||
|
|
||||||
|
The website-local routes under `src/app/api/checkout` and `src/app/api/webhooks/stripe` are intentionally disabled compatibility stubs and should not be used for production billing.
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
const nextConfig = {
|
import type { NextConfig } from "next";
|
||||||
output: "export",
|
|
||||||
images: { unoptimized: true }
|
const nextConfig: NextConfig = {
|
||||||
|
/* config options here */
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
@@ -0,0 +1,83 @@
|
|||||||
|
# DashCaddy Tutorial Project Summary
|
||||||
|
|
||||||
|
## Workflows Covered
|
||||||
|
|
||||||
|
1. **Getting Started** - Setup wizard, timezone selection, deployment mode selection
|
||||||
|
2. **Theme Customization** - Theme builder modal, preset themes, color customization
|
||||||
|
3. **Viewing Logs** - Log viewer modal, live streaming, log filtering
|
||||||
|
4. **Quick Search** - Quick search modal, keyboard shortcuts, navigation
|
||||||
|
5. **Backup & Restore** - Manual backup, automated backup, restore workflow
|
||||||
|
6. **Stats Monitoring** - Resource monitor, live stats, alerts view
|
||||||
|
|
||||||
|
## Themes Captured
|
||||||
|
|
||||||
|
- **Light theme**: Full coverage across all workflows
|
||||||
|
- **Dark theme**: Partial - theme builder modal captured, but quick theme toggle button doesn't switch themes (it's display-only)
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
**Total screenshots: 30+**
|
||||||
|
|
||||||
|
Organized by workflow:
|
||||||
|
- `01-getting-started/` - 5 screenshots (light)
|
||||||
|
- `02-deploy-app/` - 5 screenshots (light, but app selector was empty)
|
||||||
|
- `03-theme-customization/` - 5 screenshots (2 dark, 3 light)
|
||||||
|
- `04-viewing-logs/` - 4 screenshots (light)
|
||||||
|
- `05-quick-search/` - 3 screenshots (light)
|
||||||
|
- `06-backup-restore/` - 2 screenshots (light)
|
||||||
|
- `07-stats-monitoring/` - 2 screenshots (light)
|
||||||
|
- Plus additional test/diagnostic screenshots
|
||||||
|
|
||||||
|
## Files Added to dashcaddy.net Repo
|
||||||
|
|
||||||
|
### Source Tutorials (`src/tutorials/`)
|
||||||
|
- 01-getting-started.md
|
||||||
|
- 02-theme-customization.md
|
||||||
|
- 03-viewing-logs.md
|
||||||
|
- 04-quick-search.md
|
||||||
|
- 05-backup-restore.md
|
||||||
|
- 06-stats-monitoring.md
|
||||||
|
|
||||||
|
### Public Files (`public/tutorials/`)
|
||||||
|
- dashcaddy-getting-started.html (self-contained HTML guide)
|
||||||
|
- dashcaddy-getting-started.md (markdown version)
|
||||||
|
- issues.md (test instance notes)
|
||||||
|
- screenshots/ (all workflow screenshots)
|
||||||
|
|
||||||
|
## Known Gaps
|
||||||
|
|
||||||
|
1. **App Selector Empty** - The test instance App Selector modal shows no apps. Unable to document the "Deploy Your First App" workflow.
|
||||||
|
|
||||||
|
2. **Dark Theme Toggle** - The "🎨 Light" button in the top bar displays current theme but doesn't toggle. Theme switching only works through Theme Builder modal.
|
||||||
|
|
||||||
|
3. **Menu Navigation** - Some menu items in the TOOLS/STATUS/ADMIN sections weren't directly clickable in headless Chrome. Had to use JavaScript workarounds to open modals.
|
||||||
|
|
||||||
|
4. **Live Data** - Logs and Stats show "Loading..." because the test instance doesn't have actual container data.
|
||||||
|
|
||||||
|
5. **Mobile Screenshots** - Not captured (viewport 1920x1080 only).
|
||||||
|
|
||||||
|
6. **Delete/Cleanup Workflows** - Not fully explored (would need running services).
|
||||||
|
|
||||||
|
7. **Smart Arr Setup** - Not explored.
|
||||||
|
|
||||||
|
8. **Tailscale Integration** - Not explored.
|
||||||
|
|
||||||
|
9. **Docker Resources** - Not explored in detail.
|
||||||
|
|
||||||
|
## PR Information
|
||||||
|
|
||||||
|
**Branch:** `tutorials/dashcaddy-getting-started-2026-05-01`
|
||||||
|
**Base Branch:** `master`
|
||||||
|
**PR/MR URL:** https://git.dashcaddy.net/sami7777/dashcaddy.net/pulls/new/tutorials/dashcaddy-getting-started-2026-05-01
|
||||||
|
|
||||||
|
## Questions for User
|
||||||
|
|
||||||
|
1. Should tutorials be integrated into the `/docs` page, or kept as standalone downloadable content?
|
||||||
|
2. Should I integrate the tutorials into the Next.js app router (create pages at `/tutorials/*`) or keep them as static markdown files?
|
||||||
|
3. The app selector is empty in the test instance - should I skip the "Deploy First App" tutorial or mark it as needing backend configuration?
|
||||||
|
4. Is the 11MB tarball size acceptable, or should screenshots be optimized/compressed?
|
||||||
|
5. Should I try to capture mobile (390x844) screenshots, or is 1920x1080 sufficient?
|
||||||
|
|
||||||
|
## Local Backup
|
||||||
|
|
||||||
|
Tarball location: `/root/dashcaddy-tutorial.tar.gz` (11MB)
|
||||||
@@ -0,0 +1,385 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>DashCaddy Getting Started Guide</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: #1a1a2e;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: #1a1a2e;
|
||||||
|
border-bottom: 2px solid #ddd;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc h2 {
|
||||||
|
margin-top: 0;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc li {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc a {
|
||||||
|
color: #0066cc;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #0066cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card p {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-link {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
color: #0066cc;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul, ol {
|
||||||
|
margin-left: 1.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background: #f0f0f0;
|
||||||
|
padding: 0.2rem 0.4rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: 'Monaco', 'Consolas', monospace;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
kbd {
|
||||||
|
background: #e0e0e0;
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: 'Monaco', 'Consolas', monospace;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
padding: 0.75rem;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background: #f5f5f5;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
border-left: 4px solid #0066cc;
|
||||||
|
padding-left: 1rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
color: #666;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shortcut-table td:first-child {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 3rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>DashCaddy Getting Started Guide</h1>
|
||||||
|
<p class="intro">A practical guide to getting started with DashCaddy — the self-hosted app dashboard.</p>
|
||||||
|
|
||||||
|
<div class="toc">
|
||||||
|
<h2>Table of Contents</h2>
|
||||||
|
<ul>
|
||||||
|
<li><a href="#getting-started">Getting Started</a> — First-time setup and configuration</li>
|
||||||
|
<li><a href="#theme-customization">Theme Customization</a> — Customize the dashboard appearance</li>
|
||||||
|
<li><a href="#viewing-logs">Viewing Logs</a> — Monitor service logs in real-time</li>
|
||||||
|
<li><a href="#quick-search">Quick Search</a> — Fast access to services and settings</li>
|
||||||
|
<li><a href="#backup-restore">Backup & Restore</a> — Protect your configuration</li>
|
||||||
|
<li><a href="#monitoring">Monitoring Resources</a> — Track CPU, memory, and disk usage</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 id="getting-started">Getting Started</h2>
|
||||||
|
<div class="feature-card">
|
||||||
|
<h3>First-Time Setup</h3>
|
||||||
|
<p>Learn how to log in, configure your timezone, and choose a deployment mode.</p>
|
||||||
|
<p><strong>What you'll learn:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>How to log in with TOTP authentication</li>
|
||||||
|
<li>How to select your timezone</li>
|
||||||
|
<li>How to choose a deployment mode (Simple, Home Lab, Public, Custom)</li>
|
||||||
|
<li>How to complete or skip the initial setup wizard</li>
|
||||||
|
</ul>
|
||||||
|
<a class="feature-link" href="tutorials/01-getting-started.html">Read full tutorial →</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 id="theme-customization">Theme Customization</h2>
|
||||||
|
<div class="feature-card">
|
||||||
|
<h3>Customize the Dashboard</h3>
|
||||||
|
<p>Make DashCaddy your own with custom themes and colors.</p>
|
||||||
|
<p><strong>What you'll learn:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>How to open the Theme Builder</li>
|
||||||
|
<li>How to use preset themes (Dark, Light, Nord, Dracula, etc.)</li>
|
||||||
|
<li>How to customize colors for backgrounds, text, accents, and status indicators</li>
|
||||||
|
<li>How to save, import, and export themes</li>
|
||||||
|
</ul>
|
||||||
|
<a class="feature-link" href="tutorials/02-theme-customization.html">Read full tutorial →</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 id="viewing-logs">Viewing Logs</h2>
|
||||||
|
<div class="feature-card">
|
||||||
|
<h3>Monitor Service Logs</h3>
|
||||||
|
<p>Monitor your services with real-time log streaming.</p>
|
||||||
|
<p><strong>What you'll learn:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>How to access the logs viewer</li>
|
||||||
|
<li>How to navigate and filter log entries</li>
|
||||||
|
<li>How to use live log streaming</li>
|
||||||
|
<li>How to control the number of displayed log lines</li>
|
||||||
|
</ul>
|
||||||
|
<a class="feature-link" href="tutorials/03-viewing-logs.html">Read full tutorial →</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 id="quick-search">Quick Search</h2>
|
||||||
|
<div class="feature-card">
|
||||||
|
<h3>Fast Access to Everything</h3>
|
||||||
|
<p>Find services and settings instantly.</p>
|
||||||
|
<p><strong>What you'll learn:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>How to open Quick Search (<kbd>Ctrl+K</kbd> / <kbd>Cmd+K</kbd>)</li>
|
||||||
|
<li>How to search for services and settings</li>
|
||||||
|
<li>How to navigate and select results with keyboard shortcuts</li>
|
||||||
|
</ul>
|
||||||
|
<a class="feature-link" href="tutorials/04-quick-search.html">Read full tutorial →</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 id="backup-restore">Backup & Restore</h2>
|
||||||
|
<div class="feature-card">
|
||||||
|
<h3>Protect Your Configuration</h3>
|
||||||
|
<p>Create full backups of your entire DashCaddy setup.</p>
|
||||||
|
<p><strong>What you'll learn:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>How to create a manual backup</li>
|
||||||
|
<li>What's included in a full backup (services, credentials, themes)</li>
|
||||||
|
<li>How to restore from a backup file</li>
|
||||||
|
<li>How to set up automated backups</li>
|
||||||
|
<li>How to view backup history</li>
|
||||||
|
</ul>
|
||||||
|
<a class="feature-link" href="tutorials/05-backup-restore.html">Read full tutorial →</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 id="monitoring">Monitoring Resources</h2>
|
||||||
|
<div class="feature-card">
|
||||||
|
<h3>Track System Performance</h3>
|
||||||
|
<p>Monitor CPU, memory, network, and disk usage in real-time.</p>
|
||||||
|
<p><strong>What you'll learn:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>How to access the Resource Monitor</li>
|
||||||
|
<li>How to interpret live stats and historical summaries</li>
|
||||||
|
<li>How to set up alerts for resource thresholds</li>
|
||||||
|
<li>Understanding CPU, memory, network, and disk metrics</li>
|
||||||
|
</ul>
|
||||||
|
<a class="feature-link" href="tutorials/06-stats-monitoring.html">Read full tutorial →</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Keyboard Shortcuts</h2>
|
||||||
|
<table class="shortcut-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Shortcut</th>
|
||||||
|
<th>Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>Ctrl+K</kbd> / <kbd>Cmd+K</kbd></td>
|
||||||
|
<td>Open Quick Search</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>↑</kbd> / <kbd>↓</kbd></td>
|
||||||
|
<td>Navigate in Quick Search</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>Enter</kbd></td>
|
||||||
|
<td>Select in Quick Search</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>ESC</kbd></td>
|
||||||
|
<td>Close modal / Cancel</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Common Tasks</h2>
|
||||||
|
|
||||||
|
<h3>Adding Your First App</h3>
|
||||||
|
<blockquote>
|
||||||
|
<strong>Note:</strong> App deployment requires the app catalog to be configured with your DashCaddy instance.
|
||||||
|
</blockquote>
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>TOOLS</strong> → <strong>App Selector</strong></li>
|
||||||
|
<li>Browse available apps or use Quick Search</li>
|
||||||
|
<li>Select an app template</li>
|
||||||
|
<li>Configure the app settings (URL, subdomain, credentials)</li>
|
||||||
|
<li>Deploy and verify</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Checking Service Health</h3>
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>STATUS</strong> → <strong>Health Status</strong></li>
|
||||||
|
<li>View the health overview for all services</li>
|
||||||
|
<li>Check individual service status cards</li>
|
||||||
|
<li>Review any active incidents</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Updating Services</h3>
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>ADMIN</strong> → <strong>Updates</strong></li>
|
||||||
|
<li>View available updates</li>
|
||||||
|
<li>Review changelogs and release notes</li>
|
||||||
|
<li>Apply updates to individual services or all at once</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Troubleshooting</h2>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Issue</th>
|
||||||
|
<th>Solution</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Can't log in with TOTP</td>
|
||||||
|
<td>Ensure your device clock is synchronized (TOTP is time-based). Try regenerating the code — they expire every 30 seconds.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Dashboard shows no services</td>
|
||||||
|
<td>Services need to be added via the App Selector. Check that Docker is running on your server.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Logs show "Loading..."</td>
|
||||||
|
<td>The service may not have generated any logs yet. Check that the service is actually running.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Theme changes not saving</td>
|
||||||
|
<td>Click "Save Theme" before closing the Theme Builder. Try a different browser if issues persist.</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Next Steps</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Complete the initial setup wizard</li>
|
||||||
|
<li>Add your first service via App Selector</li>
|
||||||
|
<li>Customize the theme to your liking</li>
|
||||||
|
<li>Set up automated backups</li>
|
||||||
|
<li>Explore the Admin section for advanced settings</li>
|
||||||
|
<li>Join the community for tips and support</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>This guide was created for DashCaddy version as deployed on test.dashcaddy.net</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
# DashCaddy Getting Started Guide
|
||||||
|
|
||||||
|
A practical guide to getting started with DashCaddy — the self-hosted app dashboard.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Getting Started](#getting-started) — First-time setup and configuration
|
||||||
|
2. [Theme Customization](#theme-customization) — Customize the dashboard appearance
|
||||||
|
3. [Viewing Logs](#viewing-logs) — Monitor service logs in real-time
|
||||||
|
4. [Quick Search](#quick-search) — Fast access to services and settings
|
||||||
|
5. [Backup & Restore](#backup--restore) — Protect your configuration
|
||||||
|
6. [Monitoring Resources](#monitoring-resources) — Track CPU, memory, and disk usage
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
Learn how to log in, configure your timezone, and choose a deployment mode.
|
||||||
|
|
||||||
|
[Read full tutorial](tutorials/01-getting-started.md)
|
||||||
|
|
||||||
|
**What you'll learn:**
|
||||||
|
- How to log in with TOTP authentication
|
||||||
|
- How to select your timezone
|
||||||
|
- How to choose a deployment mode (Simple, Home Lab, Public, Custom)
|
||||||
|
- How to complete or skip the initial setup wizard
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Theme Customization
|
||||||
|
|
||||||
|
Make DashCaddy your own with custom themes and colors.
|
||||||
|
|
||||||
|
[Read full tutorial](tutorials/02-theme-customization.md)
|
||||||
|
|
||||||
|
**What you'll learn:**
|
||||||
|
- How to open the Theme Builder
|
||||||
|
- How to use preset themes (Dark, Light, Nord, Dracula, etc.)
|
||||||
|
- How to customize colors for backgrounds, text, accents, and status indicators
|
||||||
|
- How to save, import, and export themes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Viewing Logs
|
||||||
|
|
||||||
|
Monitor your services with real-time log streaming.
|
||||||
|
|
||||||
|
[Read full tutorial](tutorials/03-viewing-logs.md)
|
||||||
|
|
||||||
|
**What you'll learn:**
|
||||||
|
- How to access the logs viewer
|
||||||
|
- How to navigate and filter log entries
|
||||||
|
- How to use live log streaming
|
||||||
|
- How to control the number of displayed log lines
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Search
|
||||||
|
|
||||||
|
Find services and settings instantly.
|
||||||
|
|
||||||
|
[Read full tutorial](tutorials/04-quick-search.md)
|
||||||
|
|
||||||
|
**What you'll learn:**
|
||||||
|
- How to open Quick Search (Ctrl+K / Cmd+K)
|
||||||
|
- How to search for services and settings
|
||||||
|
- How to navigate and select results with keyboard shortcuts
|
||||||
|
- Keyboard shortcuts: ESC, ↑↓ Navigate, Enter Select
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Backup & Restore
|
||||||
|
|
||||||
|
Protect your configuration with full backups.
|
||||||
|
|
||||||
|
[Read full tutorial](tutorials/05-backup-restore.md)
|
||||||
|
|
||||||
|
**What you'll learn:**
|
||||||
|
- How to create a manual backup
|
||||||
|
- What's included in a full backup (services, credentials, themes)
|
||||||
|
- How to restore from a backup file
|
||||||
|
- How to set up automated backups
|
||||||
|
- How to view backup history
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Monitoring Resources
|
||||||
|
|
||||||
|
Track CPU, memory, network, and disk usage in real-time.
|
||||||
|
|
||||||
|
[Read full tutorial](tutorials/06-stats-monitoring.md)
|
||||||
|
|
||||||
|
**What you'll learn:**
|
||||||
|
- How to access the Resource Monitor
|
||||||
|
- How to interpret live stats and historical summaries
|
||||||
|
- How to set up alerts for resource thresholds
|
||||||
|
- Understanding CPU, memory, network, and disk metrics
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Tasks
|
||||||
|
|
||||||
|
### Adding Your First App
|
||||||
|
|
||||||
|
> **Note:** App deployment requires the app catalog to be configured with your DashCaddy instance.
|
||||||
|
|
||||||
|
1. Navigate to **TOOLS** → **App Selector**
|
||||||
|
2. Browse available apps or use Quick Search
|
||||||
|
3. Select an app template
|
||||||
|
4. Configure the app settings (URL, subdomain, credentials)
|
||||||
|
5. Deploy and verify
|
||||||
|
|
||||||
|
### Checking Service Health
|
||||||
|
|
||||||
|
1. Navigate to **STATUS** → **Health Status**
|
||||||
|
2. View the health overview for all services
|
||||||
|
3. Check individual service status cards
|
||||||
|
4. Review any active incidents
|
||||||
|
|
||||||
|
### Updating Services
|
||||||
|
|
||||||
|
1. Navigate to **ADMIN** → **Updates**
|
||||||
|
2. View available updates
|
||||||
|
3. Review changelogs and release notes
|
||||||
|
4. Apply updates to individual services or all at once
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Keyboard Shortcuts
|
||||||
|
|
||||||
|
| Shortcut | Action |
|
||||||
|
|----------|--------|
|
||||||
|
| `Ctrl+K` / `Cmd+K` | Open Quick Search |
|
||||||
|
| `↑` / `↓` | Navigate in Quick Search |
|
||||||
|
| `Enter` | Select in Quick Search |
|
||||||
|
| `ESC` | Close modal / Cancel |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Can't log in with TOTP
|
||||||
|
- Ensure your device clock is synchronized (TOTP is time-based)
|
||||||
|
- Try regenerating the code — they expire every 30 seconds
|
||||||
|
- Contact your administrator if the code is consistently rejected
|
||||||
|
|
||||||
|
### Dashboard shows no services
|
||||||
|
- Services need to be added via the App Selector
|
||||||
|
- Check that Docker is running on your server
|
||||||
|
- Verify network connectivity to your services
|
||||||
|
|
||||||
|
### Logs show "Loading..."
|
||||||
|
- The service may not have generated any logs yet
|
||||||
|
- Check that the service is actually running
|
||||||
|
- Verify the logging system is configured correctly
|
||||||
|
|
||||||
|
### Theme changes not saving
|
||||||
|
- Click "Save Theme" before closing the Theme Builder
|
||||||
|
- Check browser console for JavaScript errors
|
||||||
|
- Try a different browser if issues persist
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- [ ] Complete the initial setup wizard
|
||||||
|
- [ ] Add your first service via App Selector
|
||||||
|
- [ ] Customize the theme to your liking
|
||||||
|
- [ ] Set up automated backups
|
||||||
|
- [ ] Explore the Admin section for advanced settings
|
||||||
|
- [ ] Join the community for tips and support
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*This guide was created for DashCaddy version as deployed on test.dashcaddy.net*
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# Issues and Notes
|
||||||
|
|
||||||
|
## Test Instance Observations
|
||||||
|
|
||||||
|
### App Selector Empty
|
||||||
|
The App Selector modal opens but shows no apps in the grid. The "Choose an App" message appears with just a Cancel button. This could mean:
|
||||||
|
- The test instance doesn't have app templates loaded
|
||||||
|
- The app catalog API isn't responding
|
||||||
|
- The apps need to be fetched from a backend that's not configured
|
||||||
|
|
||||||
|
**Impact:** Could not complete "Deploy Your First App" workflow
|
||||||
|
|
||||||
|
**Workaround:** App selector needs backend/app catalog to be configured
|
||||||
|
|
||||||
|
### Menu Navigation Issues
|
||||||
|
The TOOLS, STATUS, and ADMIN sections in the dashboard have expandable menus. Clicking on section headers (like "TOOLS") didn't expand the menu via standard click methods in headless Chrome.
|
||||||
|
|
||||||
|
**Impact:** Could not navigate to some features through normal menu interaction
|
||||||
|
|
||||||
|
**Workaround:** Used JavaScript to directly show modals by manipulating CSS display property
|
||||||
|
|
||||||
|
### Theme Switching Not Working Properly
|
||||||
|
The "🎨 Light" button in the top bar appears to be a display of current theme rather than a toggle. Clicking it doesn't switch themes.
|
||||||
|
|
||||||
|
**Impact:** Theme workflow only partially captured - theme builder modal opens but theme switching via the quick button doesn't work
|
||||||
|
|
||||||
|
**Workaround:** Use Theme Builder modal to change themes
|
||||||
|
|
||||||
|
### Logs/Services Show "Loading..."
|
||||||
|
Some services show "Loading logs..." or "Loading container stats..." which suggests the backend APIs aren't returning data in the test instance.
|
||||||
|
|
||||||
|
**Impact:** Could see the UI for these features but not actual data
|
||||||
|
|
||||||
|
**Workaround:** Document UI even when no data is present
|
||||||
|
|
||||||
|
## Technical Observations
|
||||||
|
|
||||||
|
### Puppeteer/Chromium Issues
|
||||||
|
- Background Chromium processes were being killed by SIGKILL
|
||||||
|
- Some clickable elements weren't being recognized as clickable by puppeteer
|
||||||
|
- Had to use JavaScript clicks (`element.click()` via `page.evaluate()`) for some interactions
|
||||||
|
|
||||||
|
### Session Management
|
||||||
|
- TOTP codes expire every 30 seconds
|
||||||
|
- Had to generate fresh TOTP for each session
|
||||||
|
- Used `process.exit(0)` to avoid graceful cleanup issues that triggered process kills
|
||||||
|
|
||||||
|
## Screenshots Captured
|
||||||
|
|
||||||
|
### Workflows Documented:
|
||||||
|
1. **Getting Started** - Setup wizard, timezone, deployment mode (partial)
|
||||||
|
2. **Theme Customization** - Theme builder modal, presets (partial)
|
||||||
|
3. **Viewing Logs** - Complete workflow
|
||||||
|
4. **Quick Search** - Complete workflow
|
||||||
|
5. **Backup & Restore** - Complete workflow (UI)
|
||||||
|
6. **Stats Monitoring** - Complete workflow (UI)
|
||||||
|
|
||||||
|
### Workflows Not Fully Documented:
|
||||||
|
1. **Deploy First App** - App selector empty, couldn't complete
|
||||||
|
2. **Dark Theme Toggle** - Theme builder works but quick toggle doesn't
|
||||||
|
3. **Service Status Cards** - Not deeply explored
|
||||||
|
4. **Smart Arr Setup** - Not explored
|
||||||
|
|
||||||
|
## Recommendations
|
||||||
|
|
||||||
|
1. App Selector needs app catalog/backend to show templates
|
||||||
|
2. Menu expansion should be tested with real user interaction
|
||||||
|
3. Test instance should have some sample services running to show actual data in logs/stats
|
||||||
|
4. Consider adding a "demo mode" with fake data for tutorial purposes
|
||||||
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 160 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 175 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 175 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 175 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 86 KiB |
@@ -18,7 +18,7 @@ export default function AboutPage() {
|
|||||||
<p className="text-xl text-surface-300 max-w-2xl mx-auto">
|
<p className="text-xl text-surface-300 max-w-2xl mx-auto">
|
||||||
DashCaddy was born from the frustration of managing dozens of
|
DashCaddy was born from the frustration of managing dozens of
|
||||||
Docker containers, SSL certificates, and DNS records by hand. We
|
Docker containers, SSL certificates, and DNS records by hand. We
|
||||||
built the tool we wished existed — and then made it AI-powered.
|
built the tool we wished existed.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -45,16 +45,14 @@ export default function AboutPage() {
|
|||||||
<p>
|
<p>
|
||||||
DashCaddy was built to solve this. One click to deploy an app.
|
DashCaddy was built to solve this. One click to deploy an app.
|
||||||
SSL, DNS, and reverse proxy configuration happen automatically.
|
SSL, DNS, and reverse proxy configuration happen automatically.
|
||||||
A beautiful dashboard with real-time WebSocket updates to monitor
|
A beautiful dashboard to monitor everything. And when something
|
||||||
everything. Natural language AI commands so you can just say
|
|
||||||
“deploy Plex” and it happens. And when something
|
|
||||||
goes wrong, you know about it immediately — not when a
|
goes wrong, you know about it immediately — not when a
|
||||||
family member texts you that Plex is down.
|
family member texts you that Plex is down.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
We believe self-hosting should be accessible to everyone, not
|
We believe self-hosting should be accessible to everyone, not
|
||||||
just people who enjoy writing YAML at 2 AM. DashCaddy makes it
|
just people who enjoy writing YAML at 2 AM. DashCaddy makes it
|
||||||
beautiful, effortless, and intelligent.
|
beautiful and effortless.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -69,10 +67,10 @@ export default function AboutPage() {
|
|||||||
<div className="grid md:grid-cols-3 gap-8">
|
<div className="grid md:grid-cols-3 gap-8">
|
||||||
{[
|
{[
|
||||||
{
|
{
|
||||||
icon: "🔒",
|
icon: "🔓",
|
||||||
title: "Proprietary & Polished",
|
title: "Open Core",
|
||||||
description:
|
description:
|
||||||
"DashCaddy is proprietary software built with care. The core platform is free to use, with Premium features for advanced orchestration.",
|
"The core of DashCaddy is free and always will be. Premium features fund development, but the essentials are open to everyone.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "🏠",
|
icon: "🏠",
|
||||||
@@ -81,10 +79,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.",
|
"DashCaddy runs entirely on your hardware. No cloud dependency, no telemetry, no phoning home. Your data never leaves your network.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "🤖",
|
icon: "🛠️",
|
||||||
title: "AI-Native",
|
title: "Built to Last",
|
||||||
description:
|
description:
|
||||||
"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.",
|
"We use proven technologies — Caddy, Docker, Node.js. No bleeding-edge frameworks that break every six months. Stable, reliable, boring (in the best way).",
|
||||||
},
|
},
|
||||||
].map((value) => (
|
].map((value) => (
|
||||||
<div
|
<div
|
||||||
@@ -114,10 +112,6 @@ export default function AboutPage() {
|
|||||||
{ name: "Docker", role: "Container Runtime", icon: "🐳" },
|
{ name: "Docker", role: "Container Runtime", icon: "🐳" },
|
||||||
{ name: "Node.js", role: "API Backend", icon: "🟢" },
|
{ name: "Node.js", role: "API Backend", icon: "🟢" },
|
||||||
{ name: "Technitium", role: "DNS Server", 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 (1400+ tests)", icon: "✅" },
|
|
||||||
].map((tech) => (
|
].map((tech) => (
|
||||||
<div
|
<div
|
||||||
key={tech.name}
|
key={tech.name}
|
||||||
@@ -132,36 +126,12 @@ export default function AboutPage() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</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: "76+", label: "App Templates" },
|
|
||||||
{ value: "1400+", label: "Test Cases" },
|
|
||||||
{ value: "39", label: "SDK Methods" },
|
|
||||||
{ 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 */}
|
{/* Contact / Support */}
|
||||||
<section className="py-20 px-4 border-t border-surface-800">
|
<section className="py-20 px-4 border-t border-surface-800">
|
||||||
<div className="max-w-3xl mx-auto text-center">
|
<div className="max-w-3xl mx-auto text-center">
|
||||||
<h2 className="text-2xl font-bold text-white mb-6">Get In Touch</h2>
|
<h2 className="text-2xl font-bold text-white mb-6">Get In Touch</h2>
|
||||||
<p className="text-surface-300 mb-8">
|
<p className="text-surface-300 mb-8">
|
||||||
Have questions or feedback? We'd love to hear from you.
|
Have questions, feedback, or want to contribute? We'd love to hear from you.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
<a
|
<a
|
||||||
@@ -170,12 +140,22 @@ export default function AboutPage() {
|
|||||||
>
|
>
|
||||||
Email Us
|
Email Us
|
||||||
</a>
|
</a>
|
||||||
<Link
|
<a
|
||||||
href="/docs"
|
href="https://discord.gg/dashcaddy"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
className="px-6 py-3 rounded-lg border border-surface-700 hover:border-surface-500 text-surface-300 font-medium transition-colors"
|
className="px-6 py-3 rounded-lg border border-surface-700 hover:border-surface-500 text-surface-300 font-medium transition-colors"
|
||||||
>
|
>
|
||||||
Read the Docs
|
Join Discord
|
||||||
</Link>
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://github.com/dashcaddy"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="px-6 py-3 rounded-lg border border-surface-700 hover:border-surface-500 text-surface-300 font-medium transition-colors"
|
||||||
|
>
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -187,7 +167,7 @@ export default function AboutPage() {
|
|||||||
Ready to simplify your homelab?
|
Ready to simplify your homelab?
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-surface-300 mb-8">
|
<p className="text-surface-300 mb-8">
|
||||||
Install DashCaddy and start deploying services today. Free forever.
|
Start with the free tier. Upgrade when you're ready.
|
||||||
</p>
|
</p>
|
||||||
<Link
|
<Link
|
||||||
href="/pricing"
|
href="/pricing"
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST() {
|
||||||
return NextResponse.json({ error: 'Checkout disabled for static export' }, { status: 503 });
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: "Website-local checkout is disabled. Use the external DashCaddy license service."
|
||||||
|
},
|
||||||
|
{ status: 410 }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
|
||||||
return NextResponse.json({ error: 'Webhooks disabled for static export' }, { status: 503 });
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,10 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextResponse } from "next/server";
|
||||||
export async function POST(request: NextRequest) {
|
|
||||||
return NextResponse.json({ error: 'disabled' }, { status: 503 });
|
export async function POST() {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: "Website-local Stripe webhooks are disabled. Use the external DashCaddy license service."
|
||||||
|
},
|
||||||
|
{ status: 410 }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
import Link from 'next/link';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import Navbar from '@/components/Navbar';
|
||||||
|
import Footer from '@/components/Footer';
|
||||||
|
|
||||||
|
interface TutorialStep {
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
screenshot?: string;
|
||||||
|
screenshotAlt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TroubleshootingItem {
|
||||||
|
issue: string;
|
||||||
|
solution: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TutorialLayoutProps {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
icon: string;
|
||||||
|
heroScreenshot?: string;
|
||||||
|
steps: TutorialStep[];
|
||||||
|
troubleshooting?: TroubleshootingItem[];
|
||||||
|
prevTutorial?: { slug: string; title: string };
|
||||||
|
nextTutorial?: { slug: string; title: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TutorialLayout({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
icon,
|
||||||
|
heroScreenshot,
|
||||||
|
steps,
|
||||||
|
troubleshooting,
|
||||||
|
prevTutorial,
|
||||||
|
nextTutorial,
|
||||||
|
}: TutorialLayoutProps) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col min-h-screen bg-surface-950 text-surface-50">
|
||||||
|
<Navbar />
|
||||||
|
|
||||||
|
{/* Hero */}
|
||||||
|
<section className="relative py-12 sm:py-16">
|
||||||
|
<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-64 bg-brand-500/10 rounded-full blur-3xl opacity-30" />
|
||||||
|
</div>
|
||||||
|
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="flex items-center gap-3 mb-4">
|
||||||
|
<span className="text-3xl">{icon}</span>
|
||||||
|
<span className="text-surface-400 text-sm">
|
||||||
|
<Link href="/docs" className="hover:text-brand-400 transition-colors">Docs</Link>
|
||||||
|
{' / '}
|
||||||
|
<span className="text-surface-300">{title}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-3xl sm:text-4xl font-bold mb-4">{title}</h1>
|
||||||
|
<p className="text-lg text-surface-300 max-w-2xl">{description}</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Hero Screenshot */}
|
||||||
|
{heroScreenshot && (
|
||||||
|
<section className="py-4">
|
||||||
|
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="rounded-xl overflow-hidden border border-surface-700/50">
|
||||||
|
<Image
|
||||||
|
src={heroScreenshot}
|
||||||
|
alt={title}
|
||||||
|
width={1200}
|
||||||
|
height={675}
|
||||||
|
className="w-full h-auto"
|
||||||
|
unoptimized
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Steps */}
|
||||||
|
<section className="py-8">
|
||||||
|
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="space-y-12">
|
||||||
|
{steps.map((step, i) => (
|
||||||
|
<div key={i} className="flex flex-col sm:flex-row gap-6">
|
||||||
|
<div className="flex-shrink-0 flex items-start gap-4 sm:flex-col sm:items-center sm:w-12">
|
||||||
|
<div className="flex-shrink-0 w-8 h-8 rounded-full bg-brand-500/20 text-brand-400 flex items-center justify-center font-bold text-sm sm:w-10 sm:h-10 sm:text-base">
|
||||||
|
{i + 1}
|
||||||
|
</div>
|
||||||
|
{i < steps.length - 1 && (
|
||||||
|
<div className="hidden sm:block w-px flex-1 bg-surface-700/50 min-h-[2rem]" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 pb-8">
|
||||||
|
<h2 className="text-xl font-semibold text-surface-50 mb-3">{step.title}</h2>
|
||||||
|
<p className="text-surface-300 leading-relaxed">{step.body}</p>
|
||||||
|
{step.screenshot && (
|
||||||
|
<div className="mt-4 rounded-lg overflow-hidden border border-surface-700/50">
|
||||||
|
<Image
|
||||||
|
src={step.screenshot}
|
||||||
|
alt={step.screenshotAlt || step.title}
|
||||||
|
width={900}
|
||||||
|
height={506}
|
||||||
|
className="w-full h-auto"
|
||||||
|
unoptimized
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Troubleshooting */}
|
||||||
|
{troubleshooting && troubleshooting.length > 0 && (
|
||||||
|
<section className="py-12 bg-surface-900/30">
|
||||||
|
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<h2 className="text-2xl font-bold text-surface-50 mb-6">Common Issues</h2>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{troubleshooting.map(({ issue, solution }) => (
|
||||||
|
<div key={issue} className="p-5 rounded-lg border border-surface-700/40 bg-surface-800/20">
|
||||||
|
<div className="font-semibold text-surface-50 mb-2">{issue}</div>
|
||||||
|
<div className="text-surface-300 text-sm">{solution}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Navigation */}
|
||||||
|
<section className="py-8">
|
||||||
|
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="flex justify-between gap-4">
|
||||||
|
{prevTutorial ? (
|
||||||
|
<Link
|
||||||
|
href={`/docs/${prevTutorial.slug}`}
|
||||||
|
className="flex items-center gap-2 px-4 py-3 rounded-lg border border-surface-700/50 bg-surface-800/30 hover:border-brand-500/50 transition-colors text-surface-300 hover:text-brand-400"
|
||||||
|
>
|
||||||
|
<span>←</span>
|
||||||
|
<span>{prevTutorial.title}</span>
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<div />
|
||||||
|
)}
|
||||||
|
{nextTutorial ? (
|
||||||
|
<Link
|
||||||
|
href={`/docs/${nextTutorial.slug}`}
|
||||||
|
className="flex items-center gap-2 px-4 py-3 rounded-lg border border-surface-700/50 bg-surface-800/30 hover:border-brand-500/50 transition-colors text-surface-300 hover:text-brand-400"
|
||||||
|
>
|
||||||
|
<span>{nextTutorial.title}</span>
|
||||||
|
<span>→</span>
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<div />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Back to Docs */}
|
||||||
|
<section className="pb-12">
|
||||||
|
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<Link
|
||||||
|
href="/docs"
|
||||||
|
className="text-brand-400 hover:text-brand-300 transition-colors text-sm"
|
||||||
|
>
|
||||||
|
← Back to all documentation
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
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."
|
|
||||||
>
|
|
||||||
<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/admin operations, and backup/restore. The repository ships with an OpenAPI definition so the
|
|
||||||
public contract can mature into a full reference.
|
|
||||||
</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 $TOKEN" \\
|
|
||||||
https://dashcaddy-host/api/v1/services
|
|
||||||
|
|
||||||
# Deploy from a template
|
|
||||||
curl -X POST -H "Authorization: Bearer $TOKEN" \\
|
|
||||||
-H "Content-Type: application/json" \\
|
|
||||||
-d '{"template":" jellyfin","name":"media","hostname":"media.lab"}' \\
|
|
||||||
https://dashcaddy-host/api/v1/services`}</code></pre>
|
|
||||||
|
|
||||||
<h2>JavaScript SDK</h2>
|
|
||||||
<p>
|
|
||||||
For programmatic automation, DashCaddy ships a typed JavaScript SDK with <strong>39 methods</strong> and full
|
|
||||||
<strong> TypeScript types</strong>. It mirrors the REST API and handles authentication, retries, and structured
|
|
||||||
error handling for you.
|
|
||||||
</p>
|
|
||||||
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`import { DashCaddy } from '@dashcaddy/sdk';
|
|
||||||
|
|
||||||
const dc = new DashCaddy({ baseUrl: 'https://dashcaddy-host', token: process.env.DC_TOKEN });
|
|
||||||
|
|
||||||
// List services
|
|
||||||
const services = await dc.services.list();
|
|
||||||
|
|
||||||
// Deploy a template
|
|
||||||
const svc = await dc.services.deploy({
|
|
||||||
template: 'jellyfin',
|
|
||||||
name: 'media',
|
|
||||||
hostname: 'media.lab',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Adopt a discovered container
|
|
||||||
await dc.services.adopt({ containerId: 'abc123', hostname: 'wiki.lab' });`}</code></pre>
|
|
||||||
|
|
||||||
<h2>Structured error codes</h2>
|
|
||||||
<p>
|
|
||||||
The API and SDK return <strong>80 structured error codes</strong> rather than opaque messages, so your
|
|
||||||
automation can branch on specific failure conditions (DNS token invalid, Caddy unreachable, license expired,
|
|
||||||
etc.) instead of parsing strings.
|
|
||||||
</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 into reproducible, logged
|
|
||||||
operations.
|
|
||||||
</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" }`}</code></pre>
|
|
||||||
|
|
||||||
<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, and inspect health — all through the standard MCP tool interface.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2>WebSocket real-time updates</h2>
|
|
||||||
<p>
|
|
||||||
The dashboard subscribes to a <strong>WebSocket channel</strong> for live updates: service health changes,
|
|
||||||
container starts/stops, deployment progress, and fleet events arrive in real time without polling. You can
|
|
||||||
consume the same channel in your own dashboards or automation.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2>Prometheus metrics endpoint</h2>
|
|
||||||
<p>
|
|
||||||
DashCaddy exposes a Prometheus-format metrics endpoint at <code>/metrics</code> for service health, container
|
|
||||||
status, request counts, and system indicators. Scrape it with Prometheus and visualize in Grafana.
|
|
||||||
</p>
|
|
||||||
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Health and readiness probes
|
|
||||||
GET /healthz # liveness — is the process up?
|
|
||||||
GET /readyz # readiness — is it ready to serve (deps connected)?`}</code></pre>
|
|
||||||
|
|
||||||
<h2>Plugin & extension hooks</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>
|
|
||||||
|
|
||||||
<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 JS SDK, 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.
|
|
||||||
</p>
|
|
||||||
</DocsLayout>
|
|
||||||
<Footer />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import TutorialLayout from '../_components/TutorialLayout';
|
||||||
|
|
||||||
|
const backupRestore = {
|
||||||
|
title: 'Backup & Restore',
|
||||||
|
icon: '💾',
|
||||||
|
description: 'Protect your configuration with full backups. Export and restore services, credentials, themes, and all settings in one file.',
|
||||||
|
heroScreenshot: '/tutorials/screenshots/06-backup-restore/light/02-backup-modal-open.png',
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
title: 'Access the Backup Modal',
|
||||||
|
body: 'The Backup feature is found in the TOOLS section of the dashboard. Navigate to TOOLS and click on Backup to open the Backup & Restore modal.',
|
||||||
|
screenshot: '/tutorials/screenshots/06-backup-restore/light/01-dashboard.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'The Backup Modal Interface',
|
||||||
|
body: 'The Backup modal opens with three tabs: Manual, Automated, and History. The modal displays a title "💾 Backup & Restore" and a description: "Full backup of your entire DashCaddy setup — server config, credentials, themes, and browser preferences in one file."',
|
||||||
|
screenshot: '/tutorials/screenshots/06-backup-restore/light/02-backup-modal-open.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Create a Manual Backup',
|
||||||
|
body: 'The Manual tab is selected by default. Click "⬇️ Download Full Backup" to download a complete backup of your configuration. The backup file includes all service configurations, Caddy reverse proxy configuration, encrypted credentials, encryption keys, custom themes, and browser preferences. Save this file in a safe location.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Restore from Backup',
|
||||||
|
body: 'The 📥 Restore Backup section lets you restore from a previously saved backup file. Click the upload/restore button, select your backup file from your computer, and confirm the restore operation. Warning: restoring a backup will replace your current configuration, so export your current config first if needed.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Set Up Automated Backups',
|
||||||
|
body: 'The Automated tab lets you schedule regular backups so you always have recent restore points. Configure the backup frequency (daily, weekly, etc.) and retention settings. Automated backups ensure you can recover from configuration errors without losing recent changes.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
troubleshooting: [
|
||||||
|
{
|
||||||
|
issue: 'Download button not working',
|
||||||
|
solution: 'Check browser popup blocker settings. Try a different browser if downloads aren\'t working.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
issue: 'Restore fails',
|
||||||
|
solution: 'Verify the backup file is complete and not corrupted. Check that the file was created on the same DashCaddy version.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
issue: 'Forgotten encryption key',
|
||||||
|
solution: 'Without the encryption key from backup, some credentials may not restore. Always keep backup files and encryption keys together.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function BackupRestorePage() {
|
||||||
|
return (
|
||||||
|
<TutorialLayout
|
||||||
|
title={backupRestore.title}
|
||||||
|
description={backupRestore.description}
|
||||||
|
icon={backupRestore.icon}
|
||||||
|
heroScreenshot={backupRestore.heroScreenshot}
|
||||||
|
steps={backupRestore.steps}
|
||||||
|
troubleshooting={backupRestore.troubleshooting}
|
||||||
|
prevTutorial={{ slug: 'quick-search', title: 'Quick Search' }}
|
||||||
|
nextTutorial={{ slug: 'stats-monitoring', title: 'Stats Monitoring' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,381 +0,0 @@
|
|||||||
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 76+ 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 76+ 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 & 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 <container_name></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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import TutorialLayout from '../_components/TutorialLayout';
|
||||||
|
|
||||||
|
const gettingStarted = {
|
||||||
|
title: 'Getting Started',
|
||||||
|
icon: '🚀',
|
||||||
|
description: 'First-time setup: TOTP login, timezone configuration, and deployment mode selection. Get your dashboard ready in minutes.',
|
||||||
|
heroScreenshot: '/tutorials/screenshots/01-getting-started/light/01-initial-dashboard.png',
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
title: 'Access the Dashboard',
|
||||||
|
body: 'Navigate to your DashCaddy instance URL (e.g., https://test.dashcaddy.net/). You\'ll be greeted with a TOTP authentication overlay asking for a 6-digit code. Generate your TOTP code using your authenticator app and enter the 6 digits. The inputs auto-advance as you type each digit.',
|
||||||
|
screenshot: '/tutorials/screenshots/01-getting-started/light/02-timezone-selected.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Select Your Timezone',
|
||||||
|
body: 'After successful login, the Setup Wizard appears. The first step asks for your timezone. Scroll or search for your timezone in the dropdown (e.g., "America/Los_Angeles" for Pacific time). Click to select it.',
|
||||||
|
screenshot: '/tutorials/screenshots/01-getting-started/light/02-timezone-selected.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Choose a Deployment Mode',
|
||||||
|
body: 'Click Continue to proceed to the deployment mode selection. Choose the mode that matches your setup: Simple (IP:Port only, no DNS), Professional Home Lab (custom TLD + private DNS + internal CA), Public Server (real domain + Let\'s Encrypt), or Custom (full control). Click Continue or skip setup to access the dashboard.',
|
||||||
|
screenshot: '/tutorials/screenshots/01-getting-started/light/03-deployment-mode-selection.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Complete or Skip Setup',
|
||||||
|
body: 'Depending on your chosen mode, you may see additional configuration screens. You can fill in the requested information and click Continue, or click Skip Setup to postpone configuration and access the dashboard immediately. After completing setup, you\'ll see the main dashboard with weather widget, clock, status cards, and navigation sections (STATUS, TOOLS, ADMIN).',
|
||||||
|
screenshot: '/tutorials/screenshots/01-getting-started/light/05-after-simple-select.png',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
troubleshooting: [
|
||||||
|
{
|
||||||
|
issue: 'TOTP code rejected',
|
||||||
|
solution: 'Ensure your device clock is synchronized (TOTP is time-based). Try regenerating the code — they expire every 30 seconds.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
issue: 'Setup wizard shows no apps',
|
||||||
|
solution: 'The wizard is for initial configuration. Apps are added via TOOLS → App Selector after setup is complete.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
issue: '"Continue" button not clickable',
|
||||||
|
solution: 'Try scrolling down — the button may be below the viewport on smaller screens.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function GettingStartedPage() {
|
||||||
|
return (
|
||||||
|
<TutorialLayout
|
||||||
|
title={gettingStarted.title}
|
||||||
|
description={gettingStarted.description}
|
||||||
|
icon={gettingStarted.icon}
|
||||||
|
heroScreenshot={gettingStarted.heroScreenshot}
|
||||||
|
steps={gettingStarted.steps}
|
||||||
|
troubleshooting={gettingStarted.troubleshooting}
|
||||||
|
nextTutorial={{ slug: 'theme-customization', title: 'Theme Customization' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,391 +0,0 @@
|
|||||||
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>
|
|
||||||
|
|
||||||
<blockquote className="border-l-4 border-brand-500/50 bg-brand-500/5 p-4 rounded-r-lg mt-6">
|
|
||||||
<p className="text-surface-300">
|
|
||||||
<strong className="text-brand-400">Note:</strong> DashCaddy can run on Windows and macOS for development, but production deployments should target Linux. Docker Desktop works for testing but is not recommended for production workloads.
|
|
||||||
</p>
|
|
||||||
</blockquote>
|
|
||||||
|
|
||||||
<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>{`git clone https://github.com/samiahmed7777/dashcaddy.git
|
|
||||||
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>Clone the repository: <code>git clone https://github.com/samiahmed7777/dashcaddy.git</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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,299 +0,0 @@
|
|||||||
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>76+ 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'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 & 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's built-in internal CA auto-generates and renews certificates for every published service. For
|
|
||||||
public domains, Caddy can also use ACME (Let'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'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'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'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 & Grafana — metrics & 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 < 14</code> (certificates expiring soon).
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2>MCP Server & 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 — “restart the media server”, “deploy the postgres template”, “is the
|
|
||||||
database healthy?” — 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 & 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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,286 +0,0 @@
|
|||||||
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">76+ 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 & 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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,81 +1,417 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import Image from 'next/image';
|
||||||
import Navbar from '@/components/Navbar';
|
import Navbar from '@/components/Navbar';
|
||||||
import Footer from '@/components/Footer';
|
import Footer from '@/components/Footer';
|
||||||
|
|
||||||
const docs = [
|
const tutorials = [
|
||||||
{
|
{
|
||||||
href: '/docs/overview',
|
slug: 'getting-started',
|
||||||
title: 'Product Overview',
|
icon: '🚀',
|
||||||
description: 'What DashCaddy is, who it is for, and how the platform fits together.',
|
title: 'Getting Started',
|
||||||
|
desc: 'First-time setup: TOTP login, timezone, and deployment mode selection. Get your dashboard ready in minutes.',
|
||||||
|
screenshot: '/tutorials/screenshots/01-getting-started/light/02-timezone-selected.png',
|
||||||
|
readTime: '5 min',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: '/docs/installation',
|
slug: 'theme-customization',
|
||||||
title: 'Installation Guide',
|
icon: '🎨',
|
||||||
description: 'Installer-based and manual setup paths, prerequisites, and first launch expectations.',
|
title: 'Theme Customization',
|
||||||
|
desc: 'Customize the dashboard with 10 built-in presets (Dark, Nord, Dracula, Ocean, and more) or build your own color scheme.',
|
||||||
|
screenshot: '/tutorials/screenshots/03-theme-customization/light/04-theme-builder-open.png',
|
||||||
|
readTime: '5 min',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: '/docs/first-service',
|
slug: 'viewing-logs',
|
||||||
title: 'Deploy Your First Service',
|
icon: '📋',
|
||||||
description: 'Learn the actual deployment flow and how DashCaddy wires Docker, DNS, and Caddy together.',
|
title: 'Viewing Logs',
|
||||||
|
desc: 'Monitor your services with real-time log streaming. Filter by line count, pause live updates, and search through logs.',
|
||||||
|
screenshot: '/tutorials/screenshots/04-viewing-logs/light/02-logs-modal-open.png',
|
||||||
|
readTime: '3 min',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: '/docs/integrations',
|
slug: 'quick-search',
|
||||||
title: 'Infrastructure Integrations',
|
icon: '🔍',
|
||||||
description: 'How DashCaddy works with Docker, Caddy, Technitium DNS, DashCA, and private access workflows.',
|
title: 'Quick Search',
|
||||||
|
desc: 'Find services and settings instantly with keyboard shortcuts. Press Ctrl+K to open, arrow keys to navigate.',
|
||||||
|
screenshot: '/tutorials/screenshots/05-quick-search/light/02-quick-search-open.png',
|
||||||
|
readTime: '2 min',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: '/docs/premium',
|
slug: 'backup-restore',
|
||||||
title: 'Premium Features',
|
icon: '💾',
|
||||||
description: 'Free vs Premium, current plan model, and the exact premium-gated feature set.',
|
title: 'Backup & Restore',
|
||||||
|
desc: 'Protect your configuration with full backups. Export and restore services, credentials, themes, and all settings.',
|
||||||
|
screenshot: '/tutorials/screenshots/06-backup-restore/light/02-backup-modal-open.png',
|
||||||
|
readTime: '4 min',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: '/docs/api',
|
slug: 'stats-monitoring',
|
||||||
title: 'API and Automation',
|
icon: '📊',
|
||||||
description: 'How the API fits into service management, deployment workflows, and automation.',
|
title: 'Stats Monitoring',
|
||||||
},
|
desc: 'Track CPU, memory, network, and disk usage in real-time. View live stats, 24h summaries, and set up alerts.',
|
||||||
{
|
screenshot: '/tutorials/screenshots/07-stats-monitoring/light/02-stats-modal-open.png',
|
||||||
href: '/docs/troubleshooting',
|
readTime: '4 min',
|
||||||
title: 'Troubleshooting',
|
|
||||||
description: 'A practical debugging guide for DNS, TLS, reverse proxy, certificates, and service health.',
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function DocsHomePage() {
|
const deploymentSteps = [
|
||||||
|
{
|
||||||
|
step: '1',
|
||||||
|
title: 'Choose an App',
|
||||||
|
desc: 'Browse by category or search. Each template includes the Docker image, port mappings, volume mounts, and secrets configuration.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: '2',
|
||||||
|
title: 'Configure Routing',
|
||||||
|
desc: 'Pick subdomain mode (plex.yourdomain.com) or subdirectory mode (yourdomain.com/sonarr). Subdomain needs DNS; subdirectory works immediately.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: '3',
|
||||||
|
title: 'Set Secrets',
|
||||||
|
desc: 'DashCaddy auto-generates secure passwords for admin accounts and API keys. You can also provide your own.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: '4',
|
||||||
|
title: 'Deploy',
|
||||||
|
desc: "DashCaddy pulls the image, creates the container, configures Caddy for reverse proxy, and issues a Let's Encrypt SSL certificate. Your app is live in minutes.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const features = [
|
||||||
|
{ icon: '🚀', title: 'Deploy 75+ Apps', desc: 'Plex, Nextcloud, Jellyfin, Sonarr, and more' },
|
||||||
|
{ icon: '⚡', title: 'Automatic SSL', desc: 'Every app gets a valid HTTPS certificate' },
|
||||||
|
{ icon: '🌐', title: 'Private DNS', desc: 'Technitium DNS for .sami and .home zones' },
|
||||||
|
{ icon: '🔒', title: 'One-Click CA', desc: 'Install internal root cert on all devices' },
|
||||||
|
{ icon: '📺', title: 'Media Automation', desc: 'ARR Smart Connect: Plex + Radarr + Sonarr in one click' },
|
||||||
|
{ icon: '🐝', title: 'Docker Swarm', desc: 'Deploy across a cluster of servers (Premium)' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const featureBlocks = [
|
||||||
|
{
|
||||||
|
icon: '⚡', title: 'Automatic SSL',
|
||||||
|
body: "Caddy handles HTTPS automatically via Let's Encrypt. Every app gets a valid certificate — no manual certbot, no renewals, no expired certs.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: '🌐', title: 'Private DNS Zones',
|
||||||
|
body: 'Deploy Technitium DNS Server to manage .sami or .home zones. plex.sami, vaultwarden.sami, jellyfin.sami — all resolve internally on your network.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: '🔒', title: 'DashCA — One-Click CA',
|
||||||
|
body: 'Deploy once, visit http://ca.sami from any device, install the root certificate. All your internal *.sami domains show as fully trusted. Works on Windows, macOS, Linux, iOS, Android.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: '📺', title: 'ARR Smart Connect',
|
||||||
|
body: 'One-click setup connecting Plex + Radarr + Sonarr + Prowlarr + Seerr. Detects your Plex libraries, wires up Radarr/Sonarr automatically, configures indexers. Minutes instead of days.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: '🔐', title: 'TOTP 2FA',
|
||||||
|
body: 'The dashboard itself supports optional TOTP two-factor authentication. Enable it in Settings to protect admin access.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: '🐝', title: 'Docker Swarm (Premium)',
|
||||||
|
body: 'Scale beyond one server. Deploy apps across a cluster of multiple servers from a single DashCaddy control plane.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: '🍯', title: 'Recipes (Premium)',
|
||||||
|
body: 'One-click app stacks — deploy a complete "media server" or "homelab" in a single action instead of deploying apps one by one.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: '🔑', title: 'SSO (Premium)',
|
||||||
|
body: 'Connect Authentik as your identity provider. One login for DashCaddy, Nextcloud, Jellyfin, Gitea, and any OAuth2/OIDC-compatible app.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const appCategories = [
|
||||||
|
{ cat: 'Media Streaming', icon: '🎬', apps: 'Plex, Jellyfin, Emby, Navidrome, Calibre-Web, Kavita, Komga, Audiobookshelf, Airsonic' },
|
||||||
|
{ cat: 'Media Automation', icon: '📺', apps: 'Sonarr, Radarr, Prowlarr, Bazarr, Lidarr, Readarr, Tautulli, Seerr' },
|
||||||
|
{ cat: 'Downloads', icon: '⬇️', apps: 'qBittorrent, Transmission, SABnzbd, NZBGet, JDownloader 2' },
|
||||||
|
{ cat: 'DNS & Networking', icon: '🌐', apps: 'Technitium DNS, BIND9, PowerDNS, CoreDNS, Pi-hole, WireGuard VPN' },
|
||||||
|
{ cat: 'Security', icon: '🔒', apps: 'DashCA, Vaultwarden, Authentik, CrowdSec' },
|
||||||
|
{ cat: 'DevOps', icon: '⚙️', apps: 'Gitea, Jenkins, Drone CI, VS Code Server' },
|
||||||
|
{ cat: 'Productivity', icon: '📋', apps: 'Nextcloud, Outline, BookStack, Standard Notes, Trilium, Mealie, Excalidraw, Actual Budget' },
|
||||||
|
{ cat: 'Database', icon: '🗄️', apps: 'PostgreSQL, Redis, MongoDB, Adminer' },
|
||||||
|
{ cat: 'Photos', icon: '📷', apps: 'Immich, PhotoPrism' },
|
||||||
|
{ cat: 'Home Automation', icon: '🏠', apps: 'Home Assistant, Node-RED' },
|
||||||
|
{ cat: 'Communication', icon: '💬', apps: 'Rocket.Chat, Matrix Synapse, Roundcube, Docker Mailserver' },
|
||||||
|
{ cat: 'Gaming', icon: '🎮', apps: 'Minecraft Server, Valheim Server' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const troubleshooting = [
|
||||||
|
{
|
||||||
|
q: "App shows 'Configuring' after deployment",
|
||||||
|
a: "Check the app's logs in Dozzle or Portainer. Verify the port isn't already in use. Make sure volume mounts have correct permissions.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'SSL certificate not issued',
|
||||||
|
a: 'Verify your DNS is pointed to your server\'s IP. Check that ports 80 and 443 are open. Give Caddy 1-2 minutes after first deploy.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "Can't access app after deployment",
|
||||||
|
a: 'Try the subdomain directly (DNS may need to propagate). Check Caddy routing rules in the dashboard. Verify the container is running in Portainer.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "License won't activate",
|
||||||
|
a: 'Make sure the machine is connected to the internet. Check the machine\'s clock is set correctly. Try deactivating the old machine first if moving to a new server.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'Subscriptions not activating after payment',
|
||||||
|
a: 'The Stripe webhook can take a few minutes to arrive. If it doesn\'t arrive within 10 minutes, contact support with your Stripe payment reference.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const integrations = [
|
||||||
|
{
|
||||||
|
title: 'Technitium DNS Server', icon: '🌐',
|
||||||
|
desc: 'Self-hosted DNS with web UI. Manage private zones (.sami, .home) on your local network. Supports DNSSEC, DNS-over-HTTPS, and DNS-over-TLS.',
|
||||||
|
setup: 'Deploy the template → Access at https://dns1.sami → Create your zone → Add A records → Point your router to your DNS server',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Pi-hole', icon: '🛡️',
|
||||||
|
desc: 'Network-wide ad blocking DNS sinkhole. All DNS queries on your network pass through Pi-hole — ads and trackers get blocked at the DNS level.',
|
||||||
|
setup: 'Deploy in one click. Point your router or devices to the Pi-hole IP. No configuration needed.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'WireGuard VPN', icon: '🔐',
|
||||||
|
desc: 'Fast, modern VPN tunnel. Deploy the server, generate client config files, share with family or teammates. Full VPN access from anywhere.',
|
||||||
|
setup: 'Deploy the template → Download client configs → Share with users → Connect from any device',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'DashCA (Internal CA)', icon: '🔒',
|
||||||
|
desc: 'Built-in certificate authority. Install the root certificate once, all your internal *.sami domains are trusted on every device on your network.',
|
||||||
|
setup: 'Deploy DashCA → Visit http://ca.sami from any device → Click Install Certificate → Follow your OS instructions',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function DocsPage() {
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
|
<div className="flex flex-col min-h-screen bg-surface-950 text-surface-50">
|
||||||
<Navbar />
|
<Navbar />
|
||||||
|
|
||||||
<section className="relative overflow-hidden py-16 sm:py-20 lg:py-24">
|
{/* Hero */}
|
||||||
|
<section className="relative py-16 sm:py-20 lg:py-24">
|
||||||
<div className="absolute inset-0 -z-10">
|
<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 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>
|
||||||
<div className="mx-auto max-w-5xl px-4 sm:px-6 lg:px-8">
|
<div className="mx-auto max-w-4xl 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 sm:text-5xl lg:text-6xl font-bold mb-6">
|
||||||
<h1 className="text-4xl font-bold tracking-tight sm:text-5xl lg:text-6xl">DashCaddy Docs</h1>
|
How to Use <span className="text-brand-400">DashCaddy</span>
|
||||||
<p className="mt-6 max-w-3xl text-lg leading-8 text-surface-300">
|
</h1>
|
||||||
Everything you need to understand, install, operate, and extend DashCaddy as a real self-hosting platform.
|
<p className="text-xl text-surface-300 max-w-2xl">
|
||||||
|
Self-host Docker apps in minutes. DNS, SSL, and routing handled automatically.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="pb-20">
|
{/* Tutorials Section */}
|
||||||
<div className="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8">
|
<section className="py-12 sm:py-16 bg-surface-900/30">
|
||||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3">
|
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||||
{docs.map((doc) => (
|
<h2 className="text-3xl font-bold text-surface-50 mb-2">Step-by-Step Tutorials</h2>
|
||||||
|
<p className="text-surface-400 mb-8">
|
||||||
|
Learn the dashboard by following along. Each guide walks through a real workflow with screenshots.
|
||||||
|
</p>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
{tutorials.map((t) => (
|
||||||
<Link
|
<Link
|
||||||
key={doc.href}
|
key={t.slug}
|
||||||
href={doc.href}
|
href={`/docs/${t.slug}`}
|
||||||
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"
|
className="group block rounded-xl border border-surface-700/50 bg-surface-800/50 overflow-hidden hover:border-brand-500/50 transition-colors"
|
||||||
>
|
>
|
||||||
<h2 className="text-xl font-semibold text-surface-50">{doc.title}</h2>
|
<div className="relative h-36 bg-surface-700/30 overflow-hidden">
|
||||||
<p className="mt-3 text-sm leading-6 text-surface-300">{doc.description}</p>
|
<Image
|
||||||
<p className="mt-5 text-sm font-medium text-brand-400">Read guide →</p>
|
src={t.screenshot}
|
||||||
|
alt={t.title}
|
||||||
|
fill
|
||||||
|
className="object-cover opacity-80 group-hover:opacity-100 transition-opacity"
|
||||||
|
unoptimized
|
||||||
|
/>
|
||||||
|
<div className="absolute top-2 right-2 text-xs bg-surface-900/80 text-surface-300 px-2 py-0.5 rounded">
|
||||||
|
{t.readTime} read
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-4">
|
||||||
|
<div className="flex items-center gap-2 mb-1">
|
||||||
|
<span className="text-lg">{t.icon}</span>
|
||||||
|
<h3 className="font-semibold text-surface-50 group-hover:text-brand-400 transition-colors">{t.title}</h3>
|
||||||
|
</div>
|
||||||
|
<p className="text-surface-400 text-sm leading-relaxed">{t.desc}</p>
|
||||||
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* Table of Contents */}
|
||||||
|
<section className="py-8">
|
||||||
|
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<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">What You Can Do</h2>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
{features.map(({ icon, title, desc }) => (
|
||||||
|
<div key={title} className="flex gap-3 p-4 rounded-lg bg-surface-700/30">
|
||||||
|
<span className="text-2xl">{icon}</span>
|
||||||
|
<div>
|
||||||
|
<div className="font-semibold text-surface-50">{title}</div>
|
||||||
|
<div className="text-sm text-surface-400">{desc}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* App Templates */}
|
||||||
|
<section className="py-12 sm:py-16">
|
||||||
|
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<h2 className="text-3xl font-bold text-surface-50 mb-8">75+ App Templates</h2>
|
||||||
|
{appCategories.map(({ cat, icon, apps }) => (
|
||||||
|
<div key={cat} className="mb-6 p-5 rounded-lg border border-surface-700/40 bg-surface-800/20">
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<span>{icon}</span>
|
||||||
|
<span className="font-semibold text-brand-400">{cat}</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-surface-300 text-sm">{apps}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* How Deployment Works */}
|
||||||
|
<section className="py-12 sm:py-16 bg-surface-900/30">
|
||||||
|
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<h2 className="text-3xl font-bold text-surface-50 mb-8">How App Deployment Works</h2>
|
||||||
|
<div className="space-y-6">
|
||||||
|
{deploymentSteps.map(({ step, title, desc }) => (
|
||||||
|
<div key={step} className="flex gap-5">
|
||||||
|
<div className="flex-shrink-0 w-10 h-10 rounded-full bg-brand-500/20 text-brand-400 flex items-center justify-center font-bold text-lg">
|
||||||
|
{step}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-semibold text-surface-50 mb-2">{title}</h3>
|
||||||
|
<p className="text-surface-300">{desc}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Key Features */}
|
||||||
|
<section className="py-12 sm:py-16">
|
||||||
|
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<h2 className="text-3xl font-bold text-surface-50 mb-8">Platform Features</h2>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
{featureBlocks.map(({ icon, title, body }) => (
|
||||||
|
<div key={title} className="p-6 rounded-lg border border-surface-700/40 bg-surface-800/20">
|
||||||
|
<div className="flex items-center gap-3 mb-3">
|
||||||
|
<span className="text-2xl">{icon}</span>
|
||||||
|
<h3 className="text-lg font-semibold text-surface-50">{title}</h3>
|
||||||
|
</div>
|
||||||
|
<p className="text-surface-300 text-sm leading-relaxed">{body}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Pricing */}
|
||||||
|
<section className="py-12 sm:py-16 bg-surface-900/30">
|
||||||
|
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<h2 className="text-3xl font-bold text-surface-50 mb-8">Pricing & Licensing</h2>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
|
||||||
|
{/* Free */}
|
||||||
|
<div className="rounded-xl border border-surface-600 bg-surface-800/40 p-8">
|
||||||
|
<div className="text-surface-400 text-sm font-medium mb-1">Free</div>
|
||||||
|
<div className="text-3xl font-bold text-surface-50 mb-4">$0 <span className="text-lg font-normal text-surface-400">forever</span></div>
|
||||||
|
<ul className="space-y-3">
|
||||||
|
{[
|
||||||
|
'Deploy unlimited apps',
|
||||||
|
'75+ app templates',
|
||||||
|
'Automatic SSL certificates',
|
||||||
|
'Docker management dashboard',
|
||||||
|
'Subdomain + subdirectory routing',
|
||||||
|
'Built-in DNS integration',
|
||||||
|
'TOTP 2FA on dashboard',
|
||||||
|
].map((f) => (
|
||||||
|
<li key={f} className="flex items-start gap-2 text-surface-300 text-sm">
|
||||||
|
<span className="text-green-400 mt-0.5">✓</span>
|
||||||
|
{f}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<Link href="/pricing" className="mt-6 block text-center py-3 px-6 rounded-lg bg-surface-700 hover:bg-surface-600 text-surface-200 font-medium transition-colors">
|
||||||
|
Get Started Free
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Premium */}
|
||||||
|
<div className="rounded-xl border-2 border-brand-500/60 bg-surface-800/60 p-8 relative">
|
||||||
|
<div className="absolute -top-3 left-6 px-3 py-1 bg-brand-500 rounded-full text-xs font-bold text-white">
|
||||||
|
Premium
|
||||||
|
</div>
|
||||||
|
<div className="text-surface-400 text-sm font-medium mb-1">Premium</div>
|
||||||
|
<div className="text-3xl font-bold text-surface-50 mb-1">$25 <span className="text-lg font-normal text-surface-400">/ month</span></div>
|
||||||
|
<p className="text-surface-400 text-sm mb-4">or $99/year — all premium features</p>
|
||||||
|
<ul className="space-y-3 mb-6">
|
||||||
|
{['Everything in Core', 'SSO — Authentik single sign-on', 'Recipes — one-click app stacks', 'Docker Swarm cluster management', 'Priority support'].map((f) => (
|
||||||
|
<li key={f} className="flex items-start gap-2 text-surface-300 text-sm">
|
||||||
|
<span className="text-brand-400 mt-0.5">✓</span>
|
||||||
|
{f}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
{['premium_1m: $25/mo', 'premium_3m: $50', 'premium_6m: $65', 'premium_12m: $99/yr'].map((p) => (
|
||||||
|
<li key={p} className="flex items-start gap-2 text-surface-400 text-xs ml-4">{p}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<Link href="/pricing" className="block text-center py-3 px-6 rounded-lg bg-brand-500 hover:bg-brand-400 text-white font-semibold transition-colors">
|
||||||
|
Get Premium
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 rounded-lg bg-surface-800/40 border border-surface-700/40 text-sm text-surface-300">
|
||||||
|
<strong className="text-surface-100">License model:</strong> One machine at a time. Machine-bound fingerprint. Subscriptions cancel at period end. 7-day grace period on failed payments. Deactivate anytime to move to a new machine. No free trial.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Infrastructure Integrations */}
|
||||||
|
<section className="py-12 sm:py-16">
|
||||||
|
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<h2 className="text-3xl font-bold text-surface-50 mb-8">Infrastructure Integrations</h2>
|
||||||
|
{integrations.map(({ title, icon, desc, setup }) => (
|
||||||
|
<div key={title} className="mb-8 p-6 rounded-lg border border-surface-700/40 bg-surface-800/20">
|
||||||
|
<div className="flex items-center gap-3 mb-3">
|
||||||
|
<span className="text-2xl">{icon}</span>
|
||||||
|
<h3 className="text-xl font-semibold text-surface-50">{title}</h3>
|
||||||
|
</div>
|
||||||
|
<p className="text-surface-300 mb-3">{desc}</p>
|
||||||
|
<div className="text-sm">
|
||||||
|
<span className="text-surface-400">Setup: </span>
|
||||||
|
<span className="text-brand-400">{setup}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Troubleshooting */}
|
||||||
|
<section className="py-12 sm:py-16 bg-surface-900/30">
|
||||||
|
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<h2 className="text-3xl font-bold text-surface-50 mb-8">Troubleshooting</h2>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{troubleshooting.map(({ q, a }) => (
|
||||||
|
<div key={q} className="p-5 rounded-lg border border-surface-700/40 bg-surface-800/20">
|
||||||
|
<div className="font-semibold text-surface-50 mb-2">{q}</div>
|
||||||
|
<div className="text-surface-300 text-sm">{a}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
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."
|
|
||||||
>
|
|
||||||
<h2>Free vs Premium at a glance</h2>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Free</strong>: full dashboard, 76+ templates, service discovery, Caddy + DNS + TLS automation, real-time monitoring, Prometheus metrics, multi-user with 2FA & RBAC, Security Center, AI Intent Router, MCP Server, JS SDK, backup/restore, and single-host operation.</li>
|
|
||||||
<li><strong>Premium</strong>: everything in Free, plus Auto-Login SSO, Recipes, Docker Swarm, and Multi-Host Fleet Management — plus priority support.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2>Premium-gated features</h2>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<strong>Auto-Login SSO</strong> — single sign-on across services so authenticated users reach their apps
|
|
||||||
without repeated logins. Ideal for teams that want a seamless internal portal experience.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Recipes</strong> — deploy multi-container application stacks (e.g. an app + database + cache) as a
|
|
||||||
single coordinated unit. Recipes bundle several templates together with pre-wired networking so complex
|
|
||||||
stacks come up in one click.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Docker Swarm</strong> — multi-node orchestration. Run services across a Swarm cluster instead of a
|
|
||||||
single host, with DashCaddy managing placement, routing, and TLS across the cluster.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Multi-Host Fleet Management</strong> — manage DashCaddy deployments across multiple hosts from one
|
|
||||||
control plane. Deploy, monitor, and operate services across an entire fleet with unified visibility.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2>Pricing</h2>
|
|
||||||
<p>One Premium tier, subscription billing, no hidden upsells:</p>
|
|
||||||
<ul>
|
|
||||||
<li><strong>1 month</strong> — $25</li>
|
|
||||||
<li><strong>3 months</strong> — $50</li>
|
|
||||||
<li><strong>6 months</strong> — $65</li>
|
|
||||||
<li><strong>12 months</strong> — $99</li>
|
|
||||||
</ul>
|
|
||||||
<p>
|
|
||||||
Longer commitments are rewarded: the 12-month plan works out to roughly $8.25/month versus $25/month for a
|
|
||||||
single month.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2>Plan terms</h2>
|
|
||||||
<ul>
|
|
||||||
<li>Subscriptions only — no perpetual licenses.</li>
|
|
||||||
<li>One Premium tier (no tier ladder to navigate).</li>
|
|
||||||
<li>One active machine at a time per license.</li>
|
|
||||||
<li><strong>7-day grace period</strong> after expiry so services keep running while you renew.</li>
|
|
||||||
<li>Cancel at period end — no mid-cycle lock-in.</li>
|
|
||||||
<li>No free trial.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2>License validation</h2>
|
|
||||||
<p>
|
|
||||||
DashCaddy's licensing is built around an <strong>external validation and deactivation service</strong>,
|
|
||||||
not unlimited static license reuse. On launch and periodically thereafter, the platform validates the license
|
|
||||||
against the licensing server. This keeps licenses tied to a single active machine and supports clean
|
|
||||||
deactivation when you move hosts.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2>How to subscribe</h2>
|
|
||||||
<ol>
|
|
||||||
<li>Open the dashboard and go to <strong>Settings → Licensing</strong>.</li>
|
|
||||||
<li>Choose a plan duration and complete checkout.</li>
|
|
||||||
<li>Your license key is validated automatically — Premium features unlock immediately.</li>
|
|
||||||
<li>Manage renewal, cancellation, and machine deactivation from the same panel.</li>
|
|
||||||
</ol>
|
|
||||||
</DocsLayout>
|
|
||||||
<Footer />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import TutorialLayout from '../_components/TutorialLayout';
|
||||||
|
|
||||||
|
const quickSearch = {
|
||||||
|
title: 'Quick Search',
|
||||||
|
icon: '🔍',
|
||||||
|
description: 'Find services and settings instantly with keyboard shortcuts. Press Ctrl+K to open, arrow keys to navigate.',
|
||||||
|
heroScreenshot: '/tutorials/screenshots/05-quick-search/light/02-quick-search-open.png',
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
title: 'Open Quick Search',
|
||||||
|
body: 'Quick Search is accessed via a modal. Press Ctrl+K (or Cmd+K on Mac) to open it. Alternatively, you can access it from the dashboard header or menu if available.',
|
||||||
|
screenshot: '/tutorials/screenshots/05-quick-search/light/02-quick-search-open.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'The Quick Search Interface',
|
||||||
|
body: 'When opened, the Quick Search modal appears centered on your screen with a search input field at the top. Below the input, keyboard shortcut hints are displayed: ESC to close, ↑↓ to navigate, Enter to select, and Esc to close again.',
|
||||||
|
screenshot: '/tutorials/screenshots/05-quick-search/light/02-quick-search-open.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Search for Services or Settings',
|
||||||
|
body: 'Click on the search input field and start typing. Quick Search supports fuzzy matching and shows results as you type. Search for service names (Plex, Gitea, Nextcloud), settings (backup, theme, notifications), or dashboard features (logs, stats, docker).',
|
||||||
|
screenshot: '/tutorials/screenshots/05-quick-search/light/03-quick-search-typed.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Navigate and Select Results',
|
||||||
|
body: 'Use the keyboard to navigate through search results: ↑/↓ arrows move the selection highlight up or down, Enter selects and opens the highlighted item, and ESC closes the Quick Search modal without selecting anything. Result types include services, settings pages, dashboard features, and documentation links.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
troubleshooting: [
|
||||||
|
{
|
||||||
|
issue: "Quick Search doesn't open",
|
||||||
|
solution: 'Try the keyboard shortcut (Ctrl+K or Cmd+K). If it still doesn\'t open, the feature may not be available in your version.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
issue: 'No results appearing',
|
||||||
|
solution: 'Try shorter search terms or check your spelling. Results depend on what services and settings are configured.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
issue: "Can't select with Enter",
|
||||||
|
solution: 'Make sure a result is highlighted (has keyboard focus) before pressing Enter.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function QuickSearchPage() {
|
||||||
|
return (
|
||||||
|
<TutorialLayout
|
||||||
|
title={quickSearch.title}
|
||||||
|
description={quickSearch.description}
|
||||||
|
icon={quickSearch.icon}
|
||||||
|
heroScreenshot={quickSearch.heroScreenshot}
|
||||||
|
steps={quickSearch.steps}
|
||||||
|
troubleshooting={quickSearch.troubleshooting}
|
||||||
|
prevTutorial={{ slug: 'viewing-logs', title: 'Viewing Logs' }}
|
||||||
|
nextTutorial={{ slug: 'backup-restore', title: 'Backup & Restore' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import TutorialLayout from '../_components/TutorialLayout';
|
||||||
|
|
||||||
|
const statsMonitoring = {
|
||||||
|
title: 'Stats Monitoring',
|
||||||
|
icon: '📊',
|
||||||
|
description: 'Track CPU, memory, network, and disk usage in real-time. View live stats, 24h summaries, and set up alerts.',
|
||||||
|
heroScreenshot: '/tutorials/screenshots/07-stats-monitoring/light/02-stats-modal-open.png',
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
title: 'Access the Resource Monitor',
|
||||||
|
body: 'The Stats monitoring is found in the STATUS section of the dashboard. Navigate to STATUS and click on Stats to open the Resource Monitor.',
|
||||||
|
screenshot: '/tutorials/screenshots/07-stats-monitoring/light/01-dashboard.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'The Stats Modal Interface',
|
||||||
|
body: 'The Stats modal opens showing the 📊 Resource Monitor with a description: "Real-time and historical CPU, memory, network, and disk usage for containers." The modal has three tabs: Live Stats (real-time metrics), 24h Summary (aggregated over 24 hours), and Alerts (notifications).',
|
||||||
|
screenshot: '/tutorials/screenshots/07-stats-monitoring/light/02-stats-modal-open.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'View Live Stats',
|
||||||
|
body: 'The Live Stats tab shows real-time resource usage for each container: CPU usage percentage, memory usage (used vs. available), network I/O rates, and disk I/O rates. The display auto-refreshes every 5 seconds. Use the 🔄 Refresh Now button to manually trigger a refresh. High CPU (>80%) sustained may indicate performance issues.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Check 24h Summary',
|
||||||
|
body: 'The 24h Summary tab shows aggregated statistics over the past 24 hours: peak CPU usage, average memory usage, total network traffic, and disk usage trends. This view helps identify resource usage patterns and plan for capacity needs.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'View Alerts',
|
||||||
|
body: 'The Alerts tab shows any resource-related alerts: high CPU warnings, memory pressure alerts, disk space warnings, and network issues. Alerts help you stay on top of problems before they cause service disruptions. Configure alert thresholds in Settings if available.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
troubleshooting: [
|
||||||
|
{
|
||||||
|
issue: 'Stats show "Loading container stats..."',
|
||||||
|
solution: 'No containers are running, or Docker is not responding. Check that Docker is running and containers are active.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
issue: 'Auto-refresh not working',
|
||||||
|
solution: 'Click "🔄 Refresh Now" manually. Check that the Docker daemon is responding.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
issue: 'Missing container in stats',
|
||||||
|
solution: 'Container may have stopped. Check container status in Portainer or via the Docker CLI.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function StatsMonitoringPage() {
|
||||||
|
return (
|
||||||
|
<TutorialLayout
|
||||||
|
title={statsMonitoring.title}
|
||||||
|
description={statsMonitoring.description}
|
||||||
|
icon={statsMonitoring.icon}
|
||||||
|
heroScreenshot={statsMonitoring.heroScreenshot}
|
||||||
|
steps={statsMonitoring.steps}
|
||||||
|
troubleshooting={statsMonitoring.troubleshooting}
|
||||||
|
prevTutorial={{ slug: 'backup-restore', title: 'Backup & Restore' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import TutorialLayout from '../_components/TutorialLayout';
|
||||||
|
|
||||||
|
const themeCustomization = {
|
||||||
|
title: 'Theme Customization',
|
||||||
|
icon: '🎨',
|
||||||
|
description: 'Customize the dashboard with 10 built-in presets (Dark, Nord, Dracula, Ocean, and more) or build your own color scheme from scratch.',
|
||||||
|
heroScreenshot: '/tutorials/screenshots/03-theme-customization/light/04-theme-builder-open.png',
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
title: 'Open the Theme Builder',
|
||||||
|
body: 'Locate the theme button in the top bar of the dashboard. It\'s represented by a 🎨 icon with the text "Light" (showing your current theme). Click the 🎨 Light button or the Customize text to open the Theme Builder modal.',
|
||||||
|
screenshot: '/tutorials/screenshots/03-theme-customization/light/01-light-theme-dashboard.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Explore the Theme Builder',
|
||||||
|
body: 'The Theme Builder modal opens with several sections: a preset list (Dark, Light, Blue, Black, Nord, Dracula, Solarized Dark, Solarized Light, Taxi, Ocean), a preview card showing secondary text and accent button samples, and status indicators (Online/Offline). You can also rename your theme and see its current configuration.',
|
||||||
|
screenshot: '/tutorials/screenshots/03-theme-customization/light/04-theme-builder-open.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Select a Base Theme',
|
||||||
|
body: 'Click on one of the preset themes (e.g., "Dark") to start from that base. The preview card updates to reflect the selected preset\'s colors and styling. This gives you a starting point that you can then fine-tune.',
|
||||||
|
screenshot: '/tutorials/screenshots/03-theme-customization/dark/05-dark-preset-selected.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Customize Colors (Optional)',
|
||||||
|
body: 'Below the presets, find granular color controls organized into sections: Base Colors (Background, Card, Text, Muted Text, Border), Accent Colors (Accent, Accent Strong), and Status Colors (OK Background, OK Text, Error Bg, Error Text). Each field is editable — click on a color swatch or enter a hex value.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Save Your Theme',
|
||||||
|
body: 'When satisfied with your customizations, click Save Theme at the bottom of the modal. The theme is applied to your dashboard immediately. Click Cancel to discard changes, or ✕ to close the modal without saving. You can also use Import and Export to backup or share themes.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
troubleshooting: [
|
||||||
|
{
|
||||||
|
issue: 'Theme builder button not visible',
|
||||||
|
solution: 'Scroll to top of dashboard — the button is in the top bar next to the settings gear.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
issue: 'Preset click not working',
|
||||||
|
solution: 'Try clicking directly on the preset name text, or scroll to ensure the preset list is fully visible.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
issue: 'Theme not saving',
|
||||||
|
solution: 'Ensure you click "Save Theme" before closing the modal. If the issue persists, try a different browser.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ThemeCustomizationPage() {
|
||||||
|
return (
|
||||||
|
<TutorialLayout
|
||||||
|
title={themeCustomization.title}
|
||||||
|
description={themeCustomization.description}
|
||||||
|
icon={themeCustomization.icon}
|
||||||
|
heroScreenshot={themeCustomization.heroScreenshot}
|
||||||
|
steps={themeCustomization.steps}
|
||||||
|
troubleshooting={themeCustomization.troubleshooting}
|
||||||
|
prevTutorial={{ slug: 'getting-started', title: 'Getting Started' }}
|
||||||
|
nextTutorial={{ slug: 'viewing-logs', title: 'Viewing Logs' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
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."
|
|
||||||
>
|
|
||||||
<h2>Health check endpoints</h2>
|
|
||||||
<p>
|
|
||||||
Start every investigation with the built-in probes — they tell you whether the platform itself is healthy and
|
|
||||||
whether its dependencies are wired up:
|
|
||||||
</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>{`curl -s -o /dev/null -w "%{http_code}" https://dashcaddy-host/healthz
|
|
||||||
curl -s -o /dev/null -w "%{http_code}" https://dashcaddy-host/readyz`}</code></pre>
|
|
||||||
<p>
|
|
||||||
If <code>/healthz</code> fails, the DashCaddy process itself is down. If <code>/healthz</code> passes but
|
|
||||||
<code>/readyz</code> fails, a dependency (Docker socket, Caddy Admin API, or Technitium DNS) is unreachable.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2>The debug order</h2>
|
|
||||||
<p>Work bottom-up through the stack so you isolate the failing layer:</p>
|
|
||||||
<ol>
|
|
||||||
<li><strong>Backend container</strong> — is it running and healthy?</li>
|
|
||||||
<li><strong>Backend port</strong> — is the service listening and reachable on the host?</li>
|
|
||||||
<li><strong>Reverse proxy route</strong> — did Caddy apply the route correctly?</li>
|
|
||||||
<li><strong>DNS</strong> — does the hostname resolve to the right host?</li>
|
|
||||||
<li><strong>Certificate trust</strong> — does the client trust the CA?</li>
|
|
||||||
<li><strong>Dashboard / API state</strong> — does DashCaddy reflect reality?</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
<h2>DNS issues</h2>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Symptom</strong>: hostname does not resolve, or resolves to the wrong address.</li>
|
|
||||||
<li><strong>Check</strong>: is Technitium DNS running and is the DashCaddy API token valid?</li>
|
|
||||||
<li><strong>Check</strong>: is the record present in the correct zone? DNS automation fails silently when the zone name is wrong.</li>
|
|
||||||
<li><strong>Check</strong>: is the client using Technitium as its resolver? Public resolvers will not know about internal zones.</li>
|
|
||||||
<li><strong>Fix</strong>: re-run the DNS step from the service's action menu, or recreate the record manually 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>{`# Verify resolution against the Technitium resolver directly
|
|
||||||
dig @technitium-host media.lab
|
|
||||||
nslookup media.lab technitium-host`}</code></pre>
|
|
||||||
|
|
||||||
<h2>TLS / certificate problems</h2>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Symptom</strong>: browser shows a certificate warning or <code>NET::ERR_CERT_AUTHORITY_INVALID</code>.</li>
|
|
||||||
<li><strong>Cause</strong>: the client does not trust Caddy's internal CA / DashCA root certificate.</li>
|
|
||||||
<li><strong>Fix</strong>: download the root certificate from the <strong>DashCA</strong> page and install it as a trusted root CA on the client device. Every modern OS and browser has a slightly different import flow — the DashCA page includes per-platform instructions.</li>
|
|
||||||
<li><strong>Cause</strong>: certificate issuance failed because the Caddy Admin API was unreachable at deploy time.</li>
|
|
||||||
<li><strong>Fix</strong>: confirm the Caddy Admin API is reachable, then redeploy or re-trigger TLS for the service.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2>Reverse proxy debugging (Caddy)</h2>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Symptom</strong>: service is up and DNS resolves, but the URL returns 502/504 or does not route.</li>
|
|
||||||
<li><strong>Check</strong>: is the Caddy Admin API reachable from the DashCaddy API server?</li>
|
|
||||||
<li><strong>Check</strong>: does the Caddy route point at the correct upstream host:port? Use the Caddyfile-as-Code view 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.</li>
|
|
||||||
<li><strong>Fix</strong>: re-apply the route from the service's action menu; DashCaddy will reconcile the Caddy configuration atomically.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2>Container health</h2>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Symptom</strong>: service shows <em>Unhealthy</em> or <em>Down</em> on the dashboard.</li>
|
|
||||||
<li><strong>Check</strong>: <code>docker ps -a</code> and <code>docker logs <container></code> for crash loops or misconfiguration.</li>
|
|
||||||
<li><strong>Check</strong>: does the container's healthcheck (if defined) pass? DashCaddy surfaces container healthchecks in the UI.</li>
|
|
||||||
<li><strong>Check</strong>: are volumes and environment variables correct? Bad secrets are the most common cause of immediate exits.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2>Common gotchas</h2>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Service is down even though the dashboard is reachable</strong> — the dashboard and the service are different containers; always check the backend container directly.</li>
|
|
||||||
<li><strong>DNS automation fails silently</strong> — usually an expired or wrong-scope Technitium API token, or a mismatched zone name.</li>
|
|
||||||
<li><strong>Caddy changes are not applying</strong> — the Admin API is unavailable or the generated config is invalid; check the Caddyfile-as-Code view for errors.</li>
|
|
||||||
<li><strong>Premium features do not unlock</strong> — license validation is failing; verify the license key and that the host can reach the licensing server, and remember the one-active-machine limit.</li>
|
|
||||||
<li><strong>Internal HTTPS still warns after install</strong> — the root CA must be trusted on <em>each</em> client device, not just the server.</li>
|
|
||||||
<li><strong>WebSocket live updates stall</strong> — a reverse proxy or firewall in front of DashCaddy may be dropping the upgrade; allow WebSocket upgrades on the DashCaddy route.</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. Fixing the right layer first is always faster than
|
|
||||||
reloading the whole stack.
|
|
||||||
</p>
|
|
||||||
</DocsLayout>
|
|
||||||
<Footer />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import TutorialLayout from '../_components/TutorialLayout';
|
||||||
|
|
||||||
|
const viewingLogs = {
|
||||||
|
title: 'Viewing Logs',
|
||||||
|
icon: '📋',
|
||||||
|
description: 'Monitor your services with real-time log streaming. Filter by line count, pause live updates, and search through logs.',
|
||||||
|
heroScreenshot: '/tutorials/screenshots/04-viewing-logs/light/02-logs-modal-open.png',
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
title: 'Access the Logs Viewer',
|
||||||
|
body: 'The Logs viewer is found in the TOOLS section of the dashboard. Look for a "Logs" button (📋 Logs) in the TOOLS section. Click on it to open the logs viewer modal.',
|
||||||
|
screenshot: '/tutorials/screenshots/04-viewing-logs/light/01-dashboard.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'The Logs Modal Interface',
|
||||||
|
body: 'The logs modal displays: a DNS Logs title, a "Show" dropdown to control line count (25, 50, 100, or 200 lines), live streaming controls (📡 Live and ⏸️ Pause buttons), and a close button (✕). The main content area shows the actual log entries with timestamps, service names, and messages.',
|
||||||
|
screenshot: '/tutorials/screenshots/04-viewing-logs/light/02-logs-modal-open.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Use Live Log Streaming',
|
||||||
|
body: 'Click the 📡 Live button to enable real-time log streaming. New log entries appear automatically at the top or bottom of the list. Click ⏸️ Pause to stop auto-updating so you can scroll through logs without new entries pushing content around.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Control Log Volume',
|
||||||
|
body: 'Use the "Show" dropdown to control how many log lines are displayed at once. Choose 25 for a quick overview, 50 for the default view, 100 for more context, or 200 for detailed analysis. Fewer lines load faster and are easier to read.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
troubleshooting: [
|
||||||
|
{
|
||||||
|
issue: 'Logs show "Loading logs..."',
|
||||||
|
solution: 'No services are running, or services haven\'t generated logs yet. Check that your services are actually running in Docker.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
issue: 'Log entries not appearing',
|
||||||
|
solution: 'Live streaming may be paused — click 📡 Live to resume. Also check that the service is producing logs.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
issue: "Can't scroll through logs",
|
||||||
|
solution: 'Make sure pause (⏸️ Pause) is enabled to prevent new entries from continuously pushing content.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ViewingLogsPage() {
|
||||||
|
return (
|
||||||
|
<TutorialLayout
|
||||||
|
title={viewingLogs.title}
|
||||||
|
description={viewingLogs.description}
|
||||||
|
icon={viewingLogs.icon}
|
||||||
|
heroScreenshot={viewingLogs.heroScreenshot}
|
||||||
|
steps={viewingLogs.steps}
|
||||||
|
troubleshooting={viewingLogs.troubleshooting}
|
||||||
|
prevTutorial={{ slug: 'theme-customization', title: 'Theme Customization' }}
|
||||||
|
nextTutorial={{ slug: 'quick-search', title: 'Quick Search' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 25 KiB |
@@ -16,119 +16,79 @@ interface FeatureSection {
|
|||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
badge?: string;
|
|
||||||
features: FeatureItem[];
|
features: FeatureItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FeaturesPage() {
|
export default function FeaturesPage() {
|
||||||
const features: FeatureSection[] = [
|
const features: FeatureSection[] = [
|
||||||
{
|
{
|
||||||
id: 'ai-self-hosting',
|
id: 'deployment',
|
||||||
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',
|
title: 'App Deployment',
|
||||||
description:
|
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.',
|
||||||
'Deploy from a library of 76+ 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: '🚀',
|
icon: '🚀',
|
||||||
features: [
|
features: [
|
||||||
{ icon: '⚡', label: 'One-click deployment' },
|
{ icon: '⚡', label: 'One-click deployment' },
|
||||||
{ icon: '📦', label: '76+ app templates' },
|
{ icon: '📦', label: '50+ app templates' },
|
||||||
{ icon: '⚙️', label: 'Automatic configuration' },
|
{ icon: '⚙️', label: 'Auto-configuration' },
|
||||||
{ icon: '🔍', label: 'Service Discovery & auto-adopt' },
|
|
||||||
{ icon: '🧙', label: 'Smart Defaults Wizard' },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'ssl-security',
|
id: 'ssl-security',
|
||||||
title: 'SSL & Security',
|
title: 'SSL & Security',
|
||||||
description:
|
description: 'Enterprise-grade security built in. Automatic SSL certificates, TOTP 2FA, encrypted credentials, and comprehensive audit logs to track every action.',
|
||||||
'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: '🔒',
|
icon: '🔒',
|
||||||
features: [
|
features: [
|
||||||
{ icon: '🔐', label: 'Automatic SSL certificates' },
|
{ icon: '🔐', label: 'Automatic SSL certificates' },
|
||||||
{ icon: '📱', label: 'TOTP 2FA + email magic link' },
|
{ icon: '📱', label: 'TOTP 2FA authentication' },
|
||||||
{ icon: '👥', label: 'Multi-user admin (RBAC + invites)' },
|
{ icon: '🔑', label: 'Encrypted credentials' },
|
||||||
{ icon: '🔑', label: 'Encrypted credential vault' },
|
{ icon: '📝', label: 'Audit logs' },
|
||||||
{ icon: '📝', label: 'Audit logging' },
|
|
||||||
{ icon: '🛡️', label: 'Security Center (multi-source events)' },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'dns-networking',
|
id: 'dns',
|
||||||
title: 'DNS & Networking',
|
title: 'DNS Management',
|
||||||
description:
|
description: 'Manage all your app subdomains from one dashboard. Automatic DNS record creation with Technitium DNS integration makes domain management effortless.',
|
||||||
'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: '🌐',
|
icon: '🌐',
|
||||||
features: [
|
features: [
|
||||||
{ icon: '✨', label: 'Automatic DNS records' },
|
{ icon: '✨', label: 'Automatic DNS records' },
|
||||||
{ icon: '🔗', label: 'Technitium DNS integration' },
|
{ icon: '🔗', label: 'Technitium integration' },
|
||||||
{ icon: '🏗️', label: 'Caddyfile-as-code visual builder' },
|
{ icon: '📋', label: 'Subdomain management' },
|
||||||
{ icon: '🔁', label: 'Reverse proxy management' },
|
{ icon: '🎯', label: 'Zero DNS config' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'monitoring-ops',
|
id: 'monitoring',
|
||||||
title: 'Monitoring & Operations',
|
title: 'Monitoring & Health',
|
||||||
description:
|
description: 'Real-time visibility into your infrastructure. Monitor container health, response times, and resource usage with detailed metrics and alerts.',
|
||||||
'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: '📊',
|
icon: '📊',
|
||||||
features: [
|
features: [
|
||||||
{ icon: '🟢', label: 'Real-time health monitoring' },
|
{ icon: '🟢', label: 'Real-time status' },
|
||||||
{ icon: '⚡', label: 'WebSocket live updates' },
|
{ icon: '⏱️', label: 'Response time tracking' },
|
||||||
{ icon: '📈', label: 'Prometheus metrics export' },
|
{ icon: '💾', label: 'Resource monitoring' },
|
||||||
{ icon: '💓', label: 'Container health checks' },
|
{ icon: '📈', label: 'Performance metrics' },
|
||||||
{ icon: '⏱️', label: 'Response time & resource tracking' },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'backup-recovery',
|
id: 'docker',
|
||||||
title: 'Backup & Recovery',
|
title: 'Docker Management',
|
||||||
description:
|
description: 'Control your entire Docker environment visually. View, manage, and scale containers, access logs, and perform updates without touching the command line.',
|
||||||
'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: '🐳',
|
||||||
icon: '💾',
|
|
||||||
features: [
|
features: [
|
||||||
{ icon: '🔙', label: 'One-click backup & restore' },
|
{ icon: '🎮', label: 'Container control' },
|
||||||
{ icon: '🆘', label: 'Disaster Recovery mode' },
|
{ icon: '📜', label: 'Live logs access' },
|
||||||
{ icon: '✅', label: 'Full-system backup w/ checksums' },
|
{ icon: '🔄', label: 'Auto-updates' },
|
||||||
{ icon: '⏮️', label: 'Point-in-time restore' },
|
{ icon: '📊', label: 'Resource monitoring' },
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'developer-platform',
|
|
||||||
title: 'Developer Platform',
|
|
||||||
description:
|
|
||||||
'DashCaddy isn\'t just a UI—it\'s a platform. A typed JavaScript SDK with 39 methods, a full REST API, a plugin/extension system for custom workflows, and internationalization across 5 languages including full RTL support.',
|
|
||||||
icon: '🛠️',
|
|
||||||
features: [
|
|
||||||
{ icon: '📘', label: 'JavaScript SDK + TypeScript types (39 methods)' },
|
|
||||||
{ icon: '🔌', label: 'Plugin / Extension system' },
|
|
||||||
{ icon: '🌐', label: 'REST API' },
|
|
||||||
{ icon: '🌍', label: 'i18n: EN/ES/FR/DE/AR (RTL)' },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'premium',
|
id: 'premium',
|
||||||
title: 'Premium Features',
|
title: 'Premium Features',
|
||||||
description:
|
description: 'Advanced features for power users and production deployments. SSO integration, multi-container recipes, and Docker Swarm orchestration.',
|
||||||
'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: '⭐',
|
icon: '⭐',
|
||||||
features: [
|
features: [
|
||||||
{ icon: '🔑', label: 'Auto-Login SSO', premium: true },
|
{ icon: '🔑', label: 'Auto-Login SSO', premium: true },
|
||||||
{ icon: '📚', label: 'Recipes (multi-container stacks)', premium: true },
|
{ icon: '📚', label: 'Recipes (stack deployment)', premium: true },
|
||||||
{ icon: '🐳', label: 'Docker Swarm orchestration', premium: true },
|
{ icon: '🚀', label: 'Docker Swarm orchestration', premium: true },
|
||||||
{ icon: '📡', label: 'Multi-Host Fleet Management', premium: true },
|
|
||||||
{ icon: '🚀', label: 'Priority support', premium: true },
|
{ icon: '🚀', label: 'Priority support', premium: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -145,18 +105,11 @@ export default function FeaturesPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8 text-center">
|
<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">
|
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold mb-6">
|
||||||
Every Tool You Need to <span className="text-brand-400">Self-Host</span>
|
Powerful <span className="text-brand-400">Features</span> Built In
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-xl text-surface-300 max-w-2xl mx-auto">
|
<p className="text-xl text-surface-300 max-w-2xl mx-auto">
|
||||||
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.
|
Everything you need to manage Docker applications professionally. From deployment to monitoring, SSL to security—it's all included.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -166,22 +119,12 @@ export default function FeaturesPage() {
|
|||||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
<div className="space-y-24">
|
<div className="space-y-24">
|
||||||
{features.map((feature, idx) => (
|
{features.map((feature, idx) => (
|
||||||
<div
|
<div key={feature.id} className={`grid grid-cols-1 lg:grid-cols-2 gap-12 items-center ${idx % 2 === 1 ? 'lg:flex-row-reverse' : ''}`}>
|
||||||
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 */}
|
{/* Content Side */}
|
||||||
<div className={idx % 2 === 1 ? 'lg:order-2' : ''}>
|
<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="mb-6 inline-flex rounded-lg bg-brand-500/10 p-4 text-brand-400">
|
||||||
<div className="text-3xl">{feature.icon}</div>
|
<div className="text-3xl">{feature.icon}</div>
|
||||||
</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">
|
<h2 className="text-3xl sm:text-4xl font-bold mb-4 text-surface-50">
|
||||||
{feature.title}
|
{feature.title}
|
||||||
</h2>
|
</h2>
|
||||||
@@ -201,9 +144,7 @@ export default function FeaturesPage() {
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span className="text-xl flex-shrink-0">{item.icon}</span>
|
<span className="text-xl flex-shrink-0">{item.icon}</span>
|
||||||
<span
|
<span className={`text-sm font-medium ${item.premium ? 'text-brand-300' : 'text-surface-300'}`}>
|
||||||
className={`text-sm font-medium ${item.premium ? 'text-brand-300' : 'text-surface-300'}`}
|
|
||||||
>
|
|
||||||
{item.label}
|
{item.label}
|
||||||
{item.premium && (
|
{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">
|
<span className="ml-1 inline-block text-xs px-2 py-0.5 bg-brand-500/20 text-brand-300 rounded font-semibold">
|
||||||
@@ -223,10 +164,7 @@ export default function FeaturesPage() {
|
|||||||
<div className="relative rounded-2xl border border-surface-700/50 bg-surface-800/50 backdrop-blur p-8">
|
<div className="relative rounded-2xl border border-surface-700/50 bg-surface-800/50 backdrop-blur p-8">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{feature.features.slice(0, 3).map((item, itemIdx) => (
|
{feature.features.slice(0, 3).map((item, itemIdx) => (
|
||||||
<div
|
<div key={itemIdx} className="flex items-center gap-3 p-3 bg-surface-900/50 rounded-lg border border-surface-700/30">
|
||||||
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" />
|
<div className="w-2 h-2 rounded-full bg-brand-400" />
|
||||||
<span className="text-sm text-surface-300">{item.label}</span>
|
<span className="text-sm text-surface-300">{item.label}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -234,10 +172,7 @@ export default function FeaturesPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="mt-6 pt-6 border-t border-surface-700/30">
|
<div className="mt-6 pt-6 border-t border-surface-700/30">
|
||||||
<div className="text-sm text-surface-500 text-center">
|
<div className="text-sm text-surface-500 text-center">
|
||||||
<span className="text-brand-400 font-semibold">
|
<span className="text-brand-400 font-semibold">{feature.features.length} features</span> included
|
||||||
{feature.features.length} features
|
|
||||||
</span>{' '}
|
|
||||||
included
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -249,168 +184,112 @@ export default function FeaturesPage() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* AI Features Highlight */}
|
{/* Feature Comparison Grid */}
|
||||||
<section className="relative py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-surface-900 to-surface-950">
|
<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="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
<div className="mb-16 text-center">
|
<div className="mb-16 text-center">
|
||||||
<span className="rounded-full bg-brand-500/20 px-4 py-2 text-sm font-semibold text-brand-300">
|
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-4">
|
||||||
The Agentic Era of Self-Hosting
|
What's Included in <span className="text-brand-400">Free</span>
|
||||||
</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>
|
</h2>
|
||||||
<p className="text-lg text-surface-400 max-w-2xl mx-auto">
|
<p className="text-lg text-surface-400">
|
||||||
DashCaddy speaks AI natively. Type what you want, or let your favorite assistant do it.
|
Start with the free tier and upgrade anytime when you need advanced features.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
{/* Feature Grid */}
|
||||||
{/* AI Intent Router */}
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
<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">
|
<FeatureCard
|
||||||
<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" />
|
icon="🚀"
|
||||||
<div className="relative z-10">
|
title="One-Click Deployment"
|
||||||
<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">
|
description="Deploy from 50+ pre-configured templates with zero configuration."
|
||||||
<div className="text-3xl">🎯</div>
|
/>
|
||||||
</div>
|
<FeatureCard
|
||||||
<h3 className="mb-2 text-xl font-semibold text-surface-50">AI Intent Router</h3>
|
icon="🔒"
|
||||||
<p className="text-surface-300 text-sm leading-relaxed mb-6">
|
title="Automatic SSL"
|
||||||
Skip the menus. Just type a command in plain English and DashCaddy figures out the right actions.
|
description="Secure your apps with automatically renewed SSL certificates."
|
||||||
</p>
|
/>
|
||||||
<div className="space-y-2">
|
<FeatureCard
|
||||||
{['"deploy Plex"', '"check SSL status"', '"restart jellyfin"', '"show failing containers"'].map(
|
icon="🌐"
|
||||||
(cmd, i) => (
|
title="DNS Management"
|
||||||
<div
|
description="Automatic DNS record creation with Technitium integration."
|
||||||
key={i}
|
/>
|
||||||
className="flex items-center gap-3 rounded-lg bg-surface-900/60 border border-surface-700/30 px-3 py-2"
|
<FeatureCard
|
||||||
>
|
icon="📊"
|
||||||
<span className="text-brand-400 font-mono text-xs select-none">›</span>
|
title="Real-Time Monitoring"
|
||||||
<span className="text-sm text-surface-300 font-mono">{cmd}</span>
|
description="Track container health, response times, and resource usage."
|
||||||
</div>
|
/>
|
||||||
)
|
<FeatureCard
|
||||||
)}
|
icon="🐳"
|
||||||
</div>
|
title="Docker Control"
|
||||||
</div>
|
description="Manage containers, access logs, and updates from one dashboard."
|
||||||
</div>
|
/>
|
||||||
|
<FeatureCard
|
||||||
{/* MCP Server */}
|
icon="🔐"
|
||||||
<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">
|
title="Security Built-In"
|
||||||
<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" />
|
description="TOTP 2FA, encrypted credentials, and audit logging included."
|
||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Free vs Premium Comparison */}
|
{/* Premium Section */}
|
||||||
<section className="relative py-16 sm:py-20 lg:py-24 bg-surface-950">
|
<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="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
<div className="mb-16 text-center">
|
<div className="rounded-2xl border border-brand-500/30 bg-gradient-to-br from-brand-950/50 to-surface-900 p-12">
|
||||||
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-4">
|
<div className="max-w-3xl">
|
||||||
Free vs <span className="text-brand-400">Premium</span>
|
<div className="mb-6 inline-block">
|
||||||
</h2>
|
<span className="rounded-full bg-brand-500/20 px-4 py-2 text-sm font-semibold text-brand-300">
|
||||||
<p className="text-lg text-surface-400">
|
Premium Features
|
||||||
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">
|
|
||||||
{[
|
|
||||||
'76+ 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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-6">
|
<h2 className="text-3xl sm:text-4xl font-bold mb-6 text-surface-50">
|
||||||
<h3 className="text-2xl font-bold text-surface-50">Premium</h3>
|
Level Up With <span className="text-brand-400">Premium</span>
|
||||||
<p className="text-sm text-surface-400 mt-1">For production, multi-host, and teams.</p>
|
</h2>
|
||||||
</div>
|
<p className="text-lg text-surface-300 mb-8">
|
||||||
<p className="text-sm text-brand-300 mb-4 font-medium">
|
Get advanced features designed for power users and production deployments. Auto-Login SSO, stack recipes, Docker Swarm orchestration, and priority support.
|
||||||
Everything in Free, plus:
|
|
||||||
</p>
|
</p>
|
||||||
<ul className="space-y-3">
|
|
||||||
{[
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
|
||||||
'Auto-Login SSO for deployed apps',
|
<div className="flex items-start gap-4 p-4 rounded-lg bg-surface-800/50 border border-surface-700/30">
|
||||||
'Recipes — multi-container stack deployment',
|
<span className="text-2xl flex-shrink-0">🔑</span>
|
||||||
'Docker Swarm orchestration',
|
<div>
|
||||||
'Multi-Host Fleet Management (remote instances, parallel health probes, multi-host deploy plans)',
|
<h3 className="font-semibold text-surface-50 mb-1">Auto-Login SSO</h3>
|
||||||
'Disaster Recovery (full-system backup w/ checksums)',
|
<p className="text-sm text-surface-400">Deploy apps with automatic single sign-on integration.</p>
|
||||||
'Priority support',
|
</div>
|
||||||
].map((item, i) => (
|
</div>
|
||||||
<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">
|
<div className="flex items-start gap-4 p-4 rounded-lg bg-surface-800/50 border border-surface-700/30">
|
||||||
<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" />
|
<span className="text-2xl flex-shrink-0">📚</span>
|
||||||
</svg>
|
<div>
|
||||||
<span className="text-sm text-surface-200">{item}</span>
|
<h3 className="font-semibold text-surface-50 mb-1">Recipes</h3>
|
||||||
</li>
|
<p className="text-sm text-surface-400">Deploy multi-container stacks with one click.</p>
|
||||||
))}
|
</div>
|
||||||
</ul>
|
</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>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
href="/pricing"
|
href="/pricing"
|
||||||
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"
|
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"
|
||||||
>
|
>
|
||||||
Explore Premium Plans
|
Explore Premium Plans
|
||||||
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
|
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
|
||||||
@@ -422,7 +301,7 @@ export default function FeaturesPage() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* FAQ CTA Section */}
|
{/* FAQ Section */}
|
||||||
<section className="relative py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-surface-950 to-surface-900">
|
<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="mx-auto max-w-3xl px-4 sm:px-6 lg:px-8">
|
||||||
<div className="mb-12 text-center">
|
<div className="mb-12 text-center">
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 26 KiB |
@@ -27,25 +27,13 @@ export default function Home() {
|
|||||||
{/* Left Content */}
|
{/* Left Content */}
|
||||||
<div className="flex flex-col justify-center space-y-8">
|
<div className="flex flex-col justify-center space-y-8">
|
||||||
<div className="space-y-4">
|
<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">
|
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold tracking-tight">
|
||||||
<span className="text-brand-400">Self-Hosting</span>
|
<span className="text-brand-400">Self-Hosting</span>
|
||||||
<br />
|
<br />
|
||||||
<span>Made Intelligent</span>
|
<span>Made Beautiful</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-xl text-surface-300 max-w-lg leading-relaxed">
|
<p className="text-xl text-surface-300 max-w-lg leading-relaxed">
|
||||||
Just say “deploy Plex” — DashCaddy’s AI Intent Router handles the rest.
|
Deploy Docker apps with one click. Automatic SSL certificates, DNS management, and reverse proxy configuration—everything you need for self-hosted perfection.
|
||||||
Deploy Docker apps with one click, automatic SSL, DNS, and reverse proxy.
|
|
||||||
76+ templates, fleet management, and an MCP control plane for AI assistants.
|
|
||||||
Everything you need for self-hosted perfection.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -55,20 +43,20 @@ export default function Home() {
|
|||||||
href="/pricing"
|
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"
|
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>View Plans</span>
|
<span>View Pricing</span>
|
||||||
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
|
<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" />
|
<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>
|
</svg>
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<a
|
||||||
href="/docs"
|
href="/pricing"
|
||||||
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"
|
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"
|
||||||
>
|
>
|
||||||
<span>Read the Docs</span>
|
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
||||||
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
|
<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" />
|
||||||
<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>
|
</svg>
|
||||||
</Link>
|
Compare Plans
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Trust indicators */}
|
{/* Trust indicators */}
|
||||||
@@ -81,10 +69,6 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-surface-400">Trusted by self-hosting enthusiasts</p>
|
<p className="text-sm text-surface-400">Trusted by self-hosting enthusiasts</p>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -108,18 +92,6 @@ export default function Home() {
|
|||||||
<span className="text-xs font-mono text-surface-500">dashcaddy.local</span>
|
<span className="text-xs font-mono text-surface-500">dashcaddy.local</span>
|
||||||
</div>
|
</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">“deploy Plex”</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 */}
|
{/* Content area with animated elements */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{/* Container item 1 */}
|
{/* Container item 1 */}
|
||||||
@@ -163,8 +135,8 @@ export default function Home() {
|
|||||||
<div className="text-xs text-surface-500">Containers</div>
|
<div className="text-xs text-surface-500">Containers</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center py-2 border-l border-r border-surface-700/30">
|
<div className="text-center py-2 border-l border-r border-surface-700/30">
|
||||||
<div className="text-lg font-bold text-brand-400">100%</div>
|
<div className="text-lg font-bold text-brand-400">Premium</div>
|
||||||
<div className="text-xs text-surface-500">Uptime</div>
|
<div className="text-xs text-surface-500">Gated features</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center py-2">
|
<div className="text-center py-2">
|
||||||
<div className="text-lg font-bold text-brand-400">42GB</div>
|
<div className="text-lg font-bold text-brand-400">42GB</div>
|
||||||
@@ -188,56 +160,41 @@ export default function Home() {
|
|||||||
Powerful Features
|
Powerful Features
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-lg text-surface-400 max-w-2xl mx-auto">
|
<p className="text-lg text-surface-400 max-w-2xl mx-auto">
|
||||||
AI-native self-hosting — from one-click deploys to fleet management and a full MCP control plane.
|
Everything you need to manage your self-hosted Docker applications professionally.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Feature Grid */}
|
{/* Feature Grid */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<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 “deploy Plex” 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
|
<FeatureCard
|
||||||
icon="🚀"
|
icon="🚀"
|
||||||
title="76+ App Templates"
|
title="One-Click App Deployment"
|
||||||
description="Choose from 76+ pre-configured Docker app templates. Deploy your favorite applications instantly without manual configuration."
|
description="Choose from 50+ pre-configured Docker app templates. Deploy your favorite applications instantly without manual configuration."
|
||||||
/>
|
|
||||||
<FeatureCard
|
|
||||||
icon="🛡️"
|
|
||||||
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
|
<FeatureCard
|
||||||
icon="🔒"
|
icon="🔒"
|
||||||
title="Security & Auth"
|
title="Automatic SSL Certificates"
|
||||||
description="TOTP 2FA, email magic links, multi-user with admin invites, audit logging, and encrypted credential management built in."
|
description="Caddy internal CA automatically generates and renews SSL certificates. Secure your apps with zero configuration overhead."
|
||||||
/>
|
/>
|
||||||
<FeatureCard
|
<FeatureCard
|
||||||
icon="♻️"
|
icon="🌐"
|
||||||
title="Disaster Recovery"
|
title="DNS Integration"
|
||||||
description="Full-system backups with SHA-256 checksum verification and one-click restore. Your infrastructure is always recoverable."
|
description="Automatic DNS record creation with Technitium DNS. Manage all your app subdomains effortlessly from one dashboard."
|
||||||
/>
|
/>
|
||||||
<FeatureCard
|
<FeatureCard
|
||||||
icon="🔌"
|
icon="📊"
|
||||||
title="Plugin System"
|
title="Real-Time Monitoring"
|
||||||
description="Extend DashCaddy with custom service types, notification providers, workflow actions, and dashboard widgets. A fully extensible platform."
|
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."
|
||||||
|
/>
|
||||||
|
<FeatureCard
|
||||||
|
icon="🛡️"
|
||||||
|
title="Security First"
|
||||||
|
description="TOTP 2FA, audit logs, encrypted credentials, and role-based access control built in."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -273,10 +230,10 @@ export default function Home() {
|
|||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="text-center pt-4">
|
<div className="text-center pt-4">
|
||||||
<h3 className="text-xl font-semibold text-surface-50 mb-2">
|
<h3 className="text-xl font-semibold text-surface-50 mb-2">
|
||||||
Install & Setup Wizard
|
Install
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-surface-400">
|
<p className="text-surface-400">
|
||||||
Install DashCaddy, then answer one question — “What do you want to self-host?” The Smart Defaults Wizard matches your hardware profile and configures everything automatically.
|
Clone the DashCaddy repository and run the setup script. Takes just a few minutes on your server.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -300,7 +257,7 @@ export default function Home() {
|
|||||||
Deploy
|
Deploy
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-surface-400">
|
<p className="text-surface-400">
|
||||||
Click “Deploy” on any template, or just ask the AI Intent Router in plain English. SSL certificates and DNS records are automatically configured.
|
Click "Deploy" on any app template. SSL certificates and DNS records are automatically configured.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -320,7 +277,7 @@ export default function Home() {
|
|||||||
Done
|
Done
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-surface-400">
|
<p className="text-surface-400">
|
||||||
Your app is live with SSL and DNS configured. Monitor health in real time, manage containers, and run one-click backups from the dashboard.
|
Your app is live with SSL and DNS configured. Monitor health, manage containers, and backup from the dashboard.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -341,7 +298,7 @@ export default function Home() {
|
|||||||
Why DashCaddy?
|
Why DashCaddy?
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-lg text-surface-400 max-w-2xl mx-auto">
|
<p className="text-lg text-surface-400 max-w-2xl mx-auto">
|
||||||
Compare the pain of manual setup versus the power of an AI-native self-hosting platform.
|
Compare the pain of manual setup versus the simplicity of DashCaddy.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -366,7 +323,7 @@ export default function Home() {
|
|||||||
<tr className="hover:bg-surface-800/30 transition-colors">
|
<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-sm text-surface-300">SSL Certificates</td>
|
||||||
<td className="px-6 py-4 text-center text-sm">
|
<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's Encrypt</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 text-center text-sm">
|
<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</span>
|
||||||
@@ -378,7 +335,7 @@ export default function Home() {
|
|||||||
<span className="text-surface-500">Manual DNS records</span>
|
<span className="text-surface-500">Manual DNS records</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 text-center text-sm">
|
<td className="px-6 py-4 text-center text-sm">
|
||||||
<span className="text-green-400 font-semibold">✓ Automatic (Technitium)</span>
|
<span className="text-green-400 font-semibold">✓ Automatic</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="hover:bg-surface-800/30 transition-colors">
|
<tr className="hover:bg-surface-800/30 transition-colors">
|
||||||
@@ -387,7 +344,7 @@ export default function Home() {
|
|||||||
<span className="text-surface-500">Complex Nginx/Caddy config</span>
|
<span className="text-surface-500">Complex Nginx/Caddy config</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 text-center text-sm">
|
<td className="px-6 py-4 text-center text-sm">
|
||||||
<span className="text-green-400 font-semibold">✓ Caddyfile-as-Code builder</span>
|
<span className="text-green-400 font-semibold">✓ One-click</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="hover:bg-surface-800/30 transition-colors">
|
<tr className="hover:bg-surface-800/30 transition-colors">
|
||||||
@@ -396,61 +353,7 @@ export default function Home() {
|
|||||||
<span className="text-surface-500">Write your own compose files</span>
|
<span className="text-surface-500">Write your own compose files</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 text-center text-sm">
|
<td className="px-6 py-4 text-center text-sm">
|
||||||
<span className="text-green-400 font-semibold">✓ 76+ pre-configured</span>
|
<span className="text-green-400 font-semibold">✓ 50+ 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 & 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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="hover:bg-surface-800/30 transition-colors">
|
<tr className="hover:bg-surface-800/30 transition-colors">
|
||||||
@@ -459,11 +362,11 @@ export default function Home() {
|
|||||||
<span className="text-surface-500">Third-party tools required</span>
|
<span className="text-surface-500">Third-party tools required</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 text-center text-sm">
|
<td className="px-6 py-4 text-center text-sm">
|
||||||
<span className="text-green-400 font-semibold">✓ Built-in + Prometheus export</span>
|
<span className="text-green-400 font-semibold">✓ Built-in</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="hover:bg-surface-800/30 transition-colors">
|
<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 & Restore</td>
|
||||||
<td className="px-6 py-4 text-center text-sm">
|
<td className="px-6 py-4 text-center text-sm">
|
||||||
<span className="text-surface-500">Manual scripts and planning</span>
|
<span className="text-surface-500">Manual scripts and planning</span>
|
||||||
</td>
|
</td>
|
||||||
@@ -495,7 +398,7 @@ export default function Home() {
|
|||||||
Save Countless Hours
|
Save Countless Hours
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-surface-400 text-sm">
|
<p className="text-surface-400 text-sm">
|
||||||
What takes days of manual configuration takes minutes with DashCaddy’s Smart Defaults Wizard and automated setup.
|
What takes days of manual configuration takes minutes with DashCaddy's automated setup and configuration.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -509,7 +412,7 @@ export default function Home() {
|
|||||||
Enterprise Security
|
Enterprise Security
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-surface-400 text-sm">
|
<p className="text-surface-400 text-sm">
|
||||||
TOTP 2FA, magic links, encrypted credentials, audit logging, and a unified Security Center — built in.
|
TOTP 2FA, encrypted credentials, audit logging, and role-based access control built in.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -523,7 +426,7 @@ export default function Home() {
|
|||||||
Scale Confidently
|
Scale Confidently
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-surface-400 text-sm">
|
<p className="text-surface-400 text-sm">
|
||||||
Real-time WebSocket monitoring, Prometheus metrics export, and multi-host fleet management give you visibility across your infrastructure 24/7.
|
Real-time monitoring and health checks give you visibility into your infrastructure 24/7.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -537,7 +440,7 @@ export default function Home() {
|
|||||||
Never Lose Data
|
Never Lose Data
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-surface-400 text-sm">
|
<p className="text-surface-400 text-sm">
|
||||||
Disaster recovery with full-system backups, SHA-256 checksum verification, and one-click restore keeps your data protected.
|
One-click backup and restore functionality ensures your data is always protected and recoverable.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -546,55 +449,6 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</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">JavaScript SDK</h3>
|
|
||||||
<p className="text-xs text-surface-400 mt-1">39 methods, zero-dep</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 */}
|
{/* 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">
|
<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 */}
|
{/* Background effects */}
|
||||||
@@ -609,7 +463,7 @@ export default function Home() {
|
|||||||
<span className="text-brand-400">Self-Hosting Setup?</span>
|
<span className="text-brand-400">Self-Hosting Setup?</span>
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-xl text-surface-300 mb-12 max-w-2xl mx-auto leading-relaxed">
|
<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’s AI-native platform.
|
Join developers and self-hosting enthusiasts who've simplified their infrastructure management with DashCaddy.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
@@ -617,34 +471,34 @@ export default function Home() {
|
|||||||
href="/pricing"
|
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"
|
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"
|
||||||
>
|
>
|
||||||
Get Started
|
Start Free Today
|
||||||
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
|
<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" />
|
<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>
|
</svg>
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<a
|
||||||
href="/docs"
|
href="#"
|
||||||
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"
|
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
|
View Documentation
|
||||||
</Link>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Trust badges */}
|
{/* Trust badges */}
|
||||||
<div className="mt-12 flex flex-wrap justify-center gap-8 text-center">
|
<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>
|
||||||
<div className="text-2xl font-bold text-brand-400">Self-Hosted</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>
|
<p className="text-sm text-surface-400">Your Data, Your Rules</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="border-l border-surface-700/50" />
|
<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>
|
||||||
<div className="text-2xl font-bold text-brand-400">24/7</div>
|
<div className="text-2xl font-bold text-brand-400">24/7</div>
|
||||||
<p className="text-sm text-surface-400">Real-Time Monitoring</p>
|
<p className="text-sm text-surface-400">Community Support</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,96 +1,34 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import PricingCards from '@/components/PricingCards';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import Navbar from '@/components/Navbar';
|
import Navbar from '@/components/Navbar';
|
||||||
import Footer from '@/components/Footer';
|
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/REPLACE_30D_LINK',
|
|
||||||
'90d': 'https://buy.stripe.com/REPLACE_90D_LINK',
|
|
||||||
'180d': 'https://buy.stripe.com/REPLACE_180D_LINK',
|
|
||||||
'365d': 'https://buy.stripe.com/REPLACE_365D_LINK',
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function PricingPage() {
|
export default function PricingPage() {
|
||||||
const [selectedPlan, setSelectedPlan] = useState<'30d' | '90d' | '180d' | '365d'>('365d');
|
const [isAnnual, setIsAnnual] = useState(false);
|
||||||
|
|
||||||
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',
|
|
||||||
'76+ 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',
|
|
||||||
'JavaScript SDK + TypeScript types (39 methods)',
|
|
||||||
'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 = [
|
const faqs = [
|
||||||
{
|
{
|
||||||
question: 'How does the license work?',
|
question: 'Can I try Premium for free?',
|
||||||
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.',
|
answer: 'There is no free trial. Premium features unlock when you start a paid subscription.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
question: 'What happens when my license expires?',
|
question: 'What happens when my subscription ends?',
|
||||||
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.',
|
answer: 'If your subscription ends, DashCaddy falls back to the free tier. Your data remains intact.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
question: 'Can I use DashCaddy without Premium?',
|
question: 'Can I self-host the license server?',
|
||||||
answer: 'Absolutely. The core platform is fully functional without a license — including AI commands, the Security Center, service discovery, monitoring, backup, and all 76+ app templates. Premium unlocks SSO, Recipes, Swarm orchestration, and Fleet Management for advanced multi-node setups.',
|
answer: 'Coming soon! We\'re working on a self-hosted license server option for enterprise deployments.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
question: 'How many machines can I activate?',
|
question: 'Do you offer refunds?',
|
||||||
answer: 'One active machine at a time per license. You can deactivate and move to a new machine when needed.',
|
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?',
|
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.',
|
answer: 'DashCaddy remains self-hosted for your infrastructure. Only billing and license validation touch the hosted licensing service.',
|
||||||
},
|
|
||||||
{
|
|
||||||
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.',
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -100,152 +38,75 @@ export default function PricingPage() {
|
|||||||
<div className="flex flex-col min-h-screen bg-surface-950 text-surface-50">
|
<div className="flex flex-col min-h-screen bg-surface-950 text-surface-50">
|
||||||
<Navbar />
|
<Navbar />
|
||||||
|
|
||||||
{/* Hero */}
|
{/* Hero Section */}
|
||||||
<section className="relative py-16 sm:py-20 lg:py-24">
|
<section className="relative py-16 sm:py-20 lg:py-24">
|
||||||
<div className="absolute inset-0 -z-10">
|
<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 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>
|
||||||
|
|
||||||
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8 text-center">
|
<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">
|
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold mb-6">
|
||||||
Simple, Transparent <span className="text-brand-400">Pricing</span>
|
Simple, Transparent <span className="text-brand-400">Pricing</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-xl text-surface-300 mb-8 max-w-2xl mx-auto">
|
<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 76+ app templates. Premium unlocks advanced orchestration with a one-time payment.
|
Start free. Upgrade when you need advanced features. No surprises, no lock-in.
|
||||||
</p>
|
</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>
|
|
||||||
|
|
||||||
|
{/* 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>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* FAQ */}
|
{/* 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">
|
||||||
|
<PricingCards />
|
||||||
|
</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">
|
<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="mx-auto max-w-3xl px-4 sm:px-6 lg:px-8">
|
||||||
<div className="mb-12 text-center">
|
<div className="mb-12 text-center">
|
||||||
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-4">
|
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-4">
|
||||||
Frequently Asked <span className="text-brand-400">Questions</span>
|
Frequently Asked <span className="text-brand-400">Questions</span>
|
||||||
</h2>
|
</h2>
|
||||||
|
<p className="text-lg text-surface-400">
|
||||||
|
Have a question? We've got answers.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* FAQ Accordion */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{faqs.map((faq, idx) => (
|
{faqs.map((faq, idx) => (
|
||||||
<div
|
<div
|
||||||
key={idx}
|
key={idx}
|
||||||
className="rounded-lg border border-surface-700/50 bg-surface-800/50 overflow-hidden transition-all hover:border-surface-700"
|
className="rounded-lg border border-surface-700/50 bg-surface-800/50 overflow-hidden transition-all duration-200 hover:border-surface-700"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
onClick={() => setExpandedFaq(expandedFaq === idx ? null : idx)}
|
onClick={() => setExpandedFaq(expandedFaq === idx ? null : idx)}
|
||||||
@@ -253,12 +114,18 @@ export default function PricingPage() {
|
|||||||
>
|
>
|
||||||
<h3 className="text-lg font-semibold text-surface-50 text-left">{faq.question}</h3>
|
<h3 className="text-lg font-semibold text-surface-50 text-left">{faq.question}</h3>
|
||||||
<svg
|
<svg
|
||||||
className={`h-6 w-6 flex-shrink-0 text-brand-400 transition-transform ${expandedFaq === idx ? 'rotate-180' : ''}`}
|
className={`h-6 w-6 flex-shrink-0 text-brand-400 transition-transform duration-200 ${
|
||||||
fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor"
|
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" />
|
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{expandedFaq === idx && (
|
{expandedFaq === idx && (
|
||||||
<div className="border-t border-surface-700/50 bg-surface-900/50 px-6 py-4">
|
<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>
|
<p className="text-surface-300 leading-relaxed">{faq.answer}</p>
|
||||||
@@ -270,19 +137,21 @@ export default function PricingPage() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* CTA */}
|
{/* Final CTA */}
|
||||||
<section className="relative py-16 sm:py-20 lg:py-24 bg-surface-950">
|
<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">
|
<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>
|
<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">
|
<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.
|
Use DashCaddy free for core self-hosting, or unlock Premium for gated features.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
<Link
|
<Link
|
||||||
href="/docs/installation"
|
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 hover:shadow-lg hover:shadow-brand-500/30"
|
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"
|
||||||
>
|
>
|
||||||
Installation Guide
|
View Documentation
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
href="/"
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import Navbar from "@/components/Navbar";
|
||||||
|
import Footer from "@/components/Footer";
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Privacy Policy - DashCaddy",
|
||||||
|
description:
|
||||||
|
"How DashCaddy handles your data. We believe in privacy — your server, your data, never leaving your network.",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function PrivacyPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Navbar />
|
||||||
|
<main className="flex-1">
|
||||||
|
<section className="pt-32 pb-20 px-4">
|
||||||
|
<div className="max-w-3xl mx-auto">
|
||||||
|
<h1 className="text-4xl font-bold text-white mb-8">
|
||||||
|
Privacy Policy
|
||||||
|
</h1>
|
||||||
|
<div className="space-y-6 text-surface-300 leading-relaxed">
|
||||||
|
<p className="text-surface-400 text-sm">
|
||||||
|
Last updated: June 2025
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 className="text-xl font-semibold text-white">
|
||||||
|
Your Server, Your Data
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
DashCaddy runs entirely on your own hardware. We do not operate
|
||||||
|
any cloud services, and DashCaddy never phones home. All data
|
||||||
|
— container configurations, DNS records, SSL certificates, and
|
||||||
|
usage statistics — stays on your server.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 className="text-xl font-semibold text-white">
|
||||||
|
What We Collect
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
When you purchase a premium license, our payment processor
|
||||||
|
(Stripe) collects standard billing information. We receive only
|
||||||
|
your email address and a license key — no credit card numbers
|
||||||
|
touch our servers.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
During license activation, your server sends a one-time machine
|
||||||
|
fingerprint to generate a unique license key. This fingerprint
|
||||||
|
contains no personal information and is used solely to bind your
|
||||||
|
license to your hardware.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 className="text-xl font-semibold text-white">
|
||||||
|
License Server
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
Premium features require a license server URL to validate your
|
||||||
|
subscription. When you configure this, your server will make a
|
||||||
|
brief HTTPS call to our license server to verify your key is
|
||||||
|
active. The call includes only your license key and machine
|
||||||
|
identifier — no content of your services is ever transmitted.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The license server is hosted on infrastructure we control. We
|
||||||
|
do not sell, share, or monetise any data from this process.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 className="text-xl font-semibold text-white">
|
||||||
|
Cookies
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
The DashCaddy dashboard itself uses no tracking cookies. The
|
||||||
|
license server uses a single HttpOnly session cookie during
|
||||||
|
your browser session to manage authenticated license
|
||||||
|
management operations. No third-party analytics or advertising
|
||||||
|
cookies are used anywhere in the product.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 className="text-xl font-semibold text-white">
|
||||||
|
Third Parties
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
DashCaddy's premium checkout is processed by Stripe, Inc. Their
|
||||||
|
privacy policy governs any data you share with them. We do not
|
||||||
|
receive your payment card details under any circumstances.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 className="text-xl font-semibold text-white">
|
||||||
|
Changes to This Policy
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
If this policy changes, the date at the top of the page will
|
||||||
|
reflect the update. We will never retroactively change how we
|
||||||
|
handle data already collected.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 className="text-xl font-semibold text-white">Contact</h2>
|
||||||
|
<p>
|
||||||
|
Questions about privacy? Email{" "}
|
||||||
|
<a
|
||||||
|
href="mailto:privacy@dashcaddy.net"
|
||||||
|
className="text-brand-400 hover:text-brand-300"
|
||||||
|
>
|
||||||
|
privacy@dashcaddy.net
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<Footer />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -73,7 +73,7 @@ function SuccessContent() {
|
|||||||
4
|
4
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
Enjoy SSO, Recipes, Docker Swarm, Fleet Management, and all premium features!
|
Enjoy SSO, Recipes, Docker Swarm, and all premium features!
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import Navbar from "@/components/Navbar";
|
||||||
|
import Footer from "@/components/Footer";
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Terms of Service - DashCaddy",
|
||||||
|
description:
|
||||||
|
"The terms under which you may use DashCaddy. Plain language, no fine print surprises.",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function TermsPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Navbar />
|
||||||
|
<main className="flex-1">
|
||||||
|
<section className="pt-32 pb-20 px-4">
|
||||||
|
<div className="max-w-3xl mx-auto">
|
||||||
|
<h1 className="text-4xl font-bold text-white mb-8">
|
||||||
|
Terms of Service
|
||||||
|
</h1>
|
||||||
|
<div className="space-y-6 text-surface-300 leading-relaxed">
|
||||||
|
<p className="text-surface-400 text-sm">
|
||||||
|
Last updated: June 2025
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 className="text-xl font-semibold text-white">
|
||||||
|
Acceptance of Terms
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
By purchasing or using DashCaddy, you agree to these terms. If
|
||||||
|
you do not agree, do not use the software.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 className="text-xl font-semibold text-white">
|
||||||
|
License Grant
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
DashCaddy is provided under a commercial license. A valid
|
||||||
|
license key grants you the right to use premium features on one
|
||||||
|
server installation. Licenses are bound to your hardware and
|
||||||
|
are not transferable. A free tier covering core features is
|
||||||
|
available without a license.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 className="text-xl font-semibold text-white">
|
||||||
|
Permitted Use
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
You may use DashCaddy to manage your own servers or those of
|
||||||
|
your clients. You may not redistribute, resell, or sublicense
|
||||||
|
the software. You may not reverse engineer the license
|
||||||
|
validation system.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 className="text-xl font-semibold text-white">
|
||||||
|
Subscription & Billing
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
Subscriptions are billed in advance on a monthly, quarterly,
|
||||||
|
or annual basis depending on your plan. Cancelling a
|
||||||
|
subscription stops future billing; you retain access to
|
||||||
|
premium features until the end of your current billing
|
||||||
|
period. No refunds are offered for partial periods, unless
|
||||||
|
required by applicable law.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 className="text-xl font-semibold text-white">
|
||||||
|
Service Availability
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
DashCaddy is software you run on your own infrastructure. We
|
||||||
|
do not guarantee uptime for the license validation server. If
|
||||||
|
our license server is temporarily unavailable, premium
|
||||||
|
features gracefully degrade — your services continue to run
|
||||||
|
normally; they simply stop enforcing license eligibility
|
||||||
|
checks until connectivity is restored.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 className="text-xl font-semibold text-white">
|
||||||
|
Disclaimer
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
DashCaddy is provided "as is" without warranty of any kind.
|
||||||
|
We are not liable for any damages arising from the use of this
|
||||||
|
software. It is your responsibility to ensure your
|
||||||
|
configuration is secure and your backups are current.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 className="text-xl font-semibold text-white">
|
||||||
|
Account Termination
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
We reserve the right to revoke a license key if it is used in
|
||||||
|
violation of these terms, including use on more servers than
|
||||||
|
the license permits, or attempting to circumvent license
|
||||||
|
validation.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 className="text-xl font-semibold text-white">
|
||||||
|
Contact
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
Questions? Email{" "}
|
||||||
|
<a
|
||||||
|
href="mailto:legal@dashcaddy.net"
|
||||||
|
className="text-brand-400 hover:text-brand-300"
|
||||||
|
>
|
||||||
|
legal@dashcaddy.net
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<Footer />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -100,7 +100,7 @@ export default function AppShowcase() {
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-12 text-center">
|
<div className="mb-12 text-center">
|
||||||
<h2 className="text-3xl sm:text-4xl font-bold text-surface-50 mb-4">
|
<h2 className="text-3xl sm:text-4xl font-bold text-surface-50 mb-4">
|
||||||
76+ One-Click App Templates
|
50+ One-Click App Templates
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-lg text-surface-400 max-w-2xl mx-auto">
|
<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.
|
Deploy your favorite apps instantly with pre-configured templates, automatic SSL certificates, and integrated DNS management.
|
||||||
@@ -158,7 +158,7 @@ export default function AppShowcase() {
|
|||||||
<div className="mt-16 grid grid-cols-3 gap-4 sm:gap-8">
|
<div className="mt-16 grid grid-cols-3 gap-4 sm:gap-8">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="text-3xl sm:text-4xl font-bold text-brand-400 mb-2">
|
<div className="text-3xl sm:text-4xl font-bold text-brand-400 mb-2">
|
||||||
76+
|
50+
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-surface-400">App Templates</p>
|
<p className="text-sm text-surface-400">App Templates</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,51 @@
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
|
||||||
export default function Footer() {
|
const footerSections = [
|
||||||
const currentYear = new Date().getFullYear();
|
{
|
||||||
|
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: '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 footerSections = [
|
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: 'Discord',
|
||||||
|
href: '#',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const CURRENT_YEAR = new Date().getFullYear();
|
||||||
|
|
||||||
|
export default function Footer() {
|
||||||
|
const footerSections = [
|
||||||
{
|
{
|
||||||
title: 'Product',
|
title: 'Product',
|
||||||
links: [
|
links: [
|
||||||
@@ -17,18 +58,17 @@ export default function Footer() {
|
|||||||
{
|
{
|
||||||
title: 'Resources',
|
title: 'Resources',
|
||||||
links: [
|
links: [
|
||||||
{ label: 'Getting Started', href: '/docs/installation' },
|
{ label: 'Getting Started', href: '/docs' },
|
||||||
{ label: 'API Reference', href: '/docs/api' },
|
{ label: 'API Reference', href: '/docs#api' },
|
||||||
{ label: 'Community', href: '#' },
|
{ label: 'Discord', href: 'https://discord.gg/dashcaddy' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Support',
|
title: 'Support',
|
||||||
links: [
|
links: [
|
||||||
{ label: 'Contact', href: 'mailto:support@dashcaddy.net' },
|
{ label: 'Contact', href: 'mailto:support@dashcaddy.net' },
|
||||||
{ label: 'Docs', href: '/docs' },
|
{ label: 'Privacy Policy', href: '/privacy' },
|
||||||
{ label: 'Privacy Policy', href: '#' },
|
{ label: 'Terms of Service', href: '/terms' },
|
||||||
{ label: 'Terms of Service', href: '#' },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -40,25 +80,24 @@ export default function Footer() {
|
|||||||
<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" />
|
<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>
|
</svg>
|
||||||
),
|
),
|
||||||
label: 'Contact',
|
label: 'Discord',
|
||||||
href: 'mailto:support@dashcaddy.net',
|
href: 'https://discord.gg/dashcaddy',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="border-t border-surface-700/50 bg-surface-950">
|
<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="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">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-8 mb-8">
|
||||||
{/* Brand Section */}
|
{/* Brand Section */}
|
||||||
<div>
|
<div>
|
||||||
<Link href="/" className="flex items-center transition-opacity hover:opacity-80 mb-4">
|
<Link href="/" className="flex items-center gap-2 font-bold text-lg text-brand-400 hover:text-brand-300 transition-colors mb-4">
|
||||||
<Image src="/images/logo-dark.jpg" alt="DashCaddy" width={36} height={36} className="rounded" />
|
<span>DashCaddy</span>
|
||||||
</Link>
|
</Link>
|
||||||
<p className="text-surface-400 text-sm leading-relaxed mb-4">
|
<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.
|
Self-hosted Docker dashboard with automatic SSL, DNS, and reverse proxy. Making self-hosting beautiful and effortless.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
{socialLinks.map((link: any) => (
|
{socialLinks.map((link) => (
|
||||||
<a
|
<a
|
||||||
key={link.label}
|
key={link.label}
|
||||||
href={link.href}
|
href={link.href}
|
||||||
@@ -112,7 +151,7 @@ export default function Footer() {
|
|||||||
<div className="border-t border-surface-700/50 pt-8">
|
<div className="border-t border-surface-700/50 pt-8">
|
||||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-3">
|
<div className="flex flex-col sm:flex-row items-center justify-center gap-3">
|
||||||
<p className="text-sm text-surface-500">
|
<p className="text-sm text-surface-500">
|
||||||
© {currentYear} DashCaddy. All rights reserved. A product by
|
© {CURRENT_YEAR} DashCaddy. All rights reserved. A product by
|
||||||
</p>
|
</p>
|
||||||
<Image
|
<Image
|
||||||
src="/images/samiahmed7777-logo.png"
|
src="/images/samiahmed7777-logo.png"
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import Image from 'next/image';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
export default function Navbar() {
|
export default function Navbar() {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
const navLinks = [
|
const navLinks = [
|
||||||
{ href: '/features', label: 'Features' },
|
{ href: '#features', label: 'Features' },
|
||||||
{ href: '/pricing', label: 'Pricing' },
|
{ href: '#pricing', label: 'Pricing' },
|
||||||
{ href: '/docs', label: 'Docs' },
|
{ href: '#docs', label: 'Docs' },
|
||||||
{ href: '/about', label: 'About' },
|
{ href: '#about', label: 'About' },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -19,8 +18,9 @@ export default function Navbar() {
|
|||||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
<div className="flex h-16 items-center justify-between">
|
<div className="flex h-16 items-center justify-between">
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
<Link href="/" className="flex items-center transition-opacity hover:opacity-80">
|
<Link href="/" className="flex items-center gap-2 font-bold text-xl text-brand-400 hover:text-brand-300 transition-colors">
|
||||||
<Image src="/images/logo-dark.jpg" alt="DashCaddy" width={40} height={40} className="rounded-lg" priority />
|
<span>🚀</span>
|
||||||
|
<span>DashCaddy</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{/* Desktop Navigation */}
|
{/* Desktop Navigation */}
|
||||||
@@ -39,7 +39,7 @@ export default function Navbar() {
|
|||||||
{/* CTA Button (Desktop) */}
|
{/* CTA Button (Desktop) */}
|
||||||
<div className="hidden md:block">
|
<div className="hidden md:block">
|
||||||
<Link
|
<Link
|
||||||
href="/pricing"
|
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"
|
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
|
Get Started
|
||||||
@@ -83,7 +83,7 @@ export default function Navbar() {
|
|||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
<Link
|
<Link
|
||||||
href="/pricing"
|
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"
|
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)}
|
onClick={() => setIsOpen(false)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -0,0 +1,176 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useMemo, useState } from 'react';
|
||||||
|
import { createCheckoutSession } from '@/lib/licenseServer';
|
||||||
|
|
||||||
|
type Plan = {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
price: string;
|
||||||
|
period: string;
|
||||||
|
description: string;
|
||||||
|
savings?: string | null;
|
||||||
|
highlighted?: boolean;
|
||||||
|
features: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Premium features shared across all paid plans
|
||||||
|
const PREMIUM_FEATURES = [
|
||||||
|
'Auto-Login SSO',
|
||||||
|
'Recipes (multi-container stack deployment)',
|
||||||
|
'Docker Swarm orchestration'
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export default function PricingCards() {
|
||||||
|
const [loadingPlan, setLoadingPlan] = useState<string | null>(null);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const plans = useMemo<Plan[]>(() => [
|
||||||
|
{
|
||||||
|
code: 'free',
|
||||||
|
name: 'Free',
|
||||||
|
price: '$0',
|
||||||
|
period: 'forever',
|
||||||
|
description: 'Everything you need to self-host DashCaddy for core use.',
|
||||||
|
features: [
|
||||||
|
'Dashboard and monitoring',
|
||||||
|
'Container deployment',
|
||||||
|
'Caddy management',
|
||||||
|
'DNS management',
|
||||||
|
'Health checks',
|
||||||
|
'App templates'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'premium_1m',
|
||||||
|
name: 'Premium, 1 Month',
|
||||||
|
price: '$25',
|
||||||
|
period: 'per month',
|
||||||
|
description: 'Premium feature access for one month.',
|
||||||
|
features: [...PREMIUM_FEATURES]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'premium_3m',
|
||||||
|
name: 'Premium, 3 Months',
|
||||||
|
price: '$50',
|
||||||
|
period: 'every 3 months',
|
||||||
|
savings: 'Best short-term value',
|
||||||
|
description: 'A better value plan for regular use.',
|
||||||
|
highlighted: true,
|
||||||
|
features: [...PREMIUM_FEATURES]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'premium_6m',
|
||||||
|
name: 'Premium, 6 Months',
|
||||||
|
price: '$65',
|
||||||
|
period: 'every 6 months',
|
||||||
|
savings: 'Strong loyalty pricing',
|
||||||
|
description: 'The commitment plan with aggressive value.',
|
||||||
|
features: [...PREMIUM_FEATURES]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'premium_12m',
|
||||||
|
name: 'Premium, 12 Months',
|
||||||
|
price: '$99',
|
||||||
|
period: 'per year',
|
||||||
|
savings: 'Best overall value',
|
||||||
|
description: 'The best-value annual Premium plan.',
|
||||||
|
features: [...PREMIUM_FEATURES]
|
||||||
|
}
|
||||||
|
], []);
|
||||||
|
|
||||||
|
async function handleCheckout(planCode: string) {
|
||||||
|
try {
|
||||||
|
setError(null);
|
||||||
|
setLoadingPlan(planCode);
|
||||||
|
const result = await createCheckoutSession(planCode);
|
||||||
|
window.location.href = result.url;
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : 'Unable to start checkout');
|
||||||
|
} finally {
|
||||||
|
setLoadingPlan(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
{error && (
|
||||||
|
<div className="rounded-lg border border-red-500/40 bg-red-500/10 px-4 py-3 text-sm text-red-200">
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-5 gap-6 max-w-7xl mx-auto">
|
||||||
|
{plans.map((plan) => {
|
||||||
|
const isFree = plan.code === 'free';
|
||||||
|
const isLoading = loadingPlan === plan.code;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={plan.code}
|
||||||
|
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'
|
||||||
|
: 'border-surface-700/50 bg-surface-800/50 hover:border-surface-700 hover:bg-surface-800/80'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{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">
|
||||||
|
Recommended
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="p-8">
|
||||||
|
<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>
|
||||||
|
<div className="flex items-baseline gap-2 mb-2">
|
||||||
|
<span className="text-4xl font-bold text-surface-50">{plan.price}</span>
|
||||||
|
<span className="text-surface-400 text-sm">/{plan.period}</span>
|
||||||
|
</div>
|
||||||
|
{plan.savings && <p className="text-sm text-brand-400 font-semibold">{plan.savings}</p>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isFree ? (
|
||||||
|
<a
|
||||||
|
href="/docs"
|
||||||
|
className="block w-full rounded-lg px-6 py-3 text-center font-semibold transition-all duration-200 mb-8 border border-surface-700 bg-surface-700/50 text-surface-50 hover:border-brand-400 hover:bg-surface-700 hover:text-brand-400"
|
||||||
|
>
|
||||||
|
Use Free Tier
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
onClick={() => handleCheckout(plan.code)}
|
||||||
|
disabled={Boolean(loadingPlan)}
|
||||||
|
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'
|
||||||
|
} disabled:opacity-60 disabled:cursor-not-allowed`}
|
||||||
|
>
|
||||||
|
{isLoading ? 'Opening Checkout...' : 'Buy Premium'}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="border-t border-surface-700/50 pt-8">
|
||||||
|
<ul className="space-y-4">
|
||||||
|
{plan.features.map((feature) => (
|
||||||
|
<li key={feature} 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>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
export const LICENSE_SERVER_URL =
|
||||||
|
process.env.NEXT_PUBLIC_LICENSE_SERVER_URL || 'https://licenses.dashcaddy.net';
|
||||||
|
|
||||||
|
export async function createCheckoutSession(planCode: string, customerEmail?: string) {
|
||||||
|
const response = await fetch(`${LICENSE_SERVER_URL}/api/checkout/session`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ planCode, customerEmail })
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok || !data?.url) {
|
||||||
|
throw new Error(data?.error || 'Unable to start checkout');
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
# Getting Started with DashCaddy
|
||||||
|
|
||||||
|
This tutorial walks you through the initial setup of DashCaddy — logging in, configuring your timezone, and choosing a deployment mode. By the end, you'll have your dashboard ready to use.
|
||||||
|
|
||||||
|
**Prerequisites:** Access to a DashCaddy instance with TOTP credentials.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1: Access the Dashboard
|
||||||
|
|
||||||
|
Navigate to your DashCaddy instance URL (e.g., `https://test.dashcaddy.net/`). You'll be greeted with a TOTP authentication overlay asking for a 6-digit code.
|
||||||
|
|
||||||
|
**What you should see:**
|
||||||
|
- A centered 6-digit input field
|
||||||
|
- The DashCaddy logo above the input
|
||||||
|
- An empty error message area below
|
||||||
|
|
||||||
|
Generate your TOTP code using your authenticator app (the secret was provided during account setup) and enter the 6 digits. The inputs auto-advance as you type each digit.
|
||||||
|
|
||||||
|
**Troubleshooting:**
|
||||||
|
- If you see "Invalid code" error, check that your device clock is synchronized (TOTP is time-based)
|
||||||
|
- If the input doesn't accept digits, refresh the page and try again
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2: Select Your Timezone
|
||||||
|
|
||||||
|
After successful login, the Setup Wizard appears. The first step asks for your timezone.
|
||||||
|
|
||||||
|
**What you should see:**
|
||||||
|
- Welcome message: "Welcome to DashCaddy! 🎉"
|
||||||
|
- "Let's set up your self-hosted app dashboard. This will only take a few minutes."
|
||||||
|
- "Your Timezone" dropdown with a long list of timezones
|
||||||
|
|
||||||
|
Scroll or search for your timezone in the dropdown (e.g., "America/Los_Angeles" for Pacific time). Click to select it.
|
||||||
|
|
||||||
|
Screenshot: `screenshots/01-getting-started/light/02-timezone-selected.png`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3: Choose Deployment Mode
|
||||||
|
|
||||||
|
After setting your timezone, click **Continue →** to proceed to the deployment mode selection.
|
||||||
|
|
||||||
|
**What you should see:**
|
||||||
|
- "Professional Home Lab Setup" (default selection)
|
||||||
|
- Options for:
|
||||||
|
- **🚀 Simple** — Access apps via IP:Port only. No DNS or SSL setup needed.
|
||||||
|
- **🏠 Professional Home Lab** — Custom TLD + Private DNS + Internal CA. Full HTTPS with your own certificate authority.
|
||||||
|
- **🌍 Public Server** — Real domain + Public DNS + Let's Encrypt. Internet-accessible with trusted SSL.
|
||||||
|
- **⚙️ Custom** — I'll configure everything myself. Full control over every setting.
|
||||||
|
|
||||||
|
Screenshot: `screenshots/01-getting-started/light/03-deployment-mode-selection.png`
|
||||||
|
|
||||||
|
Choose the mode that matches your setup:
|
||||||
|
- For home lab use without a domain: **Simple**
|
||||||
|
- For a home lab with custom domain (e.g., `.home`): **Professional Home Lab**
|
||||||
|
- For a public server with real domain: **Public Server**
|
||||||
|
- For maximum control: **Custom**
|
||||||
|
|
||||||
|
Click **Continue →** to proceed or **← Back** to return to the previous step.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4: Complete or Skip Setup
|
||||||
|
|
||||||
|
Depending on your chosen mode, you may see additional configuration screens. You can:
|
||||||
|
|
||||||
|
- Fill in the requested information and click **Continue →**
|
||||||
|
- Or click **Skip Setup** to postpone configuration and access the dashboard immediately
|
||||||
|
|
||||||
|
**What you should see:**
|
||||||
|
After completing or skipping setup, you'll see the main dashboard with:
|
||||||
|
- Weather widget at top left
|
||||||
|
- Clock showing current time
|
||||||
|
- Status cards in the main area
|
||||||
|
- Navigation sections: STATUS, TOOLS, ADMIN
|
||||||
|
|
||||||
|
Screenshot: `screenshots/01-getting-started/light/01-initial-dashboard.png`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Issues
|
||||||
|
|
||||||
|
| Issue | Solution |
|
||||||
|
|-------|----------|
|
||||||
|
| TOTP code rejected | Ensure your device time is correct; try regenerating the code |
|
||||||
|
| Setup wizard shows no apps | The wizard is for initial configuration; apps are added via TOOLS → App Selector |
|
||||||
|
| Timezone not saving | The timezone should auto-save; refresh and check if it persisted |
|
||||||
|
| "Continue" button not clickable | Try scrolling down — the button may be below the viewport |
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
# Customizing the Dashboard Theme
|
||||||
|
|
||||||
|
DashCaddy includes a powerful theme builder that lets you customize colors, accents, and visual elements. This tutorial shows how to access and use the theme builder.
|
||||||
|
|
||||||
|
**Prerequisites:** Logged into DashCaddy dashboard.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1: Open the Theme Builder
|
||||||
|
|
||||||
|
Locate the theme button in the top bar of the dashboard. It's represented by a 🎨 icon with the text "Light" (showing your current theme).
|
||||||
|
|
||||||
|
**What you should see:**
|
||||||
|
- Top bar with: Weather widget, Clock, Settings gear, Theme toggle (🎨 Light), License badge
|
||||||
|
|
||||||
|
Click the **🎨 Light** button (or click **Customize** text if visible) to open the Theme Builder modal.
|
||||||
|
|
||||||
|
Screenshot: `screenshots/03-theme-customization/light/01-light-theme-dashboard.png`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2: Explore the Theme Builder
|
||||||
|
|
||||||
|
The Theme Builder modal opens with several sections:
|
||||||
|
|
||||||
|
**Header:**
|
||||||
|
- Theme Builder title
|
||||||
|
- Edit dropdown (currently editing "New Theme")
|
||||||
|
- Delete button
|
||||||
|
- Name field for your theme
|
||||||
|
|
||||||
|
**Start From Presets:**
|
||||||
|
A list of built-in theme presets you can start from:
|
||||||
|
- **Dark** — Dark background with light text
|
||||||
|
- **Light** — Light background
|
||||||
|
- **Blue** — Blue-tinted theme
|
||||||
|
- **Black** — Pure black background
|
||||||
|
- **Nord** — Nord color palette
|
||||||
|
- **Dracula** — Dracula color scheme
|
||||||
|
- **Solarized Dark** / **Solarized Light** — Solarized themes
|
||||||
|
- **Taxi** — Yellow-tinted theme
|
||||||
|
- **Ocean** — Ocean blue theme
|
||||||
|
|
||||||
|
**Preview Area:**
|
||||||
|
A sample card showing how your theme looks, including:
|
||||||
|
- Secondary text preview
|
||||||
|
- Accent Button preview
|
||||||
|
- Status indicators (Online/Offline)
|
||||||
|
|
||||||
|
Screenshot: `screenshots/03-theme-customization/light/04-theme-builder-open.png`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3: Select a Base Theme
|
||||||
|
|
||||||
|
Click on one of the preset themes (e.g., "Dark") to start from that base. The preview will update to show how the selected preset looks.
|
||||||
|
|
||||||
|
**What you should see:**
|
||||||
|
The preview card updates to reflect the selected preset's colors. The "Start from" selection highlights your choice.
|
||||||
|
|
||||||
|
Screenshot: `screenshots/03-theme-customization/dark/05-dark-preset-selected.png`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4: Customize Colors (Optional)
|
||||||
|
|
||||||
|
Below the presets, you'll find granular color controls organized into sections:
|
||||||
|
|
||||||
|
**Base Colors:**
|
||||||
|
- Background — Main background color
|
||||||
|
- Card — Card/panel background color
|
||||||
|
- Text — Primary text color
|
||||||
|
- Muted Text — Secondary/hint text color
|
||||||
|
- Border — Border and divider color
|
||||||
|
|
||||||
|
**Accent Colors:**
|
||||||
|
- Accent — Primary accent color (buttons, links, highlights)
|
||||||
|
- Accent Strong — Deeper accent for emphasis
|
||||||
|
|
||||||
|
**Status Colors:**
|
||||||
|
- OK Background — Background for success/OK indicators
|
||||||
|
- OK Text — Text color for success states
|
||||||
|
- Error Bg — Background for error indicators
|
||||||
|
- Error Text — Text color for error states
|
||||||
|
|
||||||
|
Each color field is editable — click on a color swatch or enter a hex value to customize.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 5: Save Your Theme
|
||||||
|
|
||||||
|
When you're satisfied with your customizations:
|
||||||
|
|
||||||
|
1. Click **Save Theme** at the bottom of the modal
|
||||||
|
2. The theme will be applied to your dashboard immediately
|
||||||
|
3. Click **Cancel** to discard changes, or **✕** to close the modal
|
||||||
|
|
||||||
|
Screenshot: `screenshots/03-theme-customization/dark/02-dark-theme-dashboard.png`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Importing and Exporting Themes
|
||||||
|
|
||||||
|
The Theme Builder supports theme import/export for backup or sharing:
|
||||||
|
|
||||||
|
- **Import** — Load a previously exported theme file (.json)
|
||||||
|
- **Export** — Download your current theme as a file for backup or sharing
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Issues
|
||||||
|
|
||||||
|
| Issue | Solution |
|
||||||
|
|-------|----------|
|
||||||
|
| Theme builder button not visible | Scroll to top of dashboard — the button is in the top bar |
|
||||||
|
| Colors not updating in preview | Click directly on a color swatch to edit it |
|
||||||
|
| Theme not saving | Ensure you click "Save Theme" before closing the modal |
|
||||||
|
| Preset click not working | Try clicking directly on the preset name text |
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# Viewing Service Logs
|
||||||
|
|
||||||
|
DashCaddy provides a centralized log viewer that lets you monitor logs from your services in real-time. This tutorial shows how to access and use the logs viewer.
|
||||||
|
|
||||||
|
**Prerequisites:** Logged into DashCaddy dashboard with running services.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1: Access the Logs Viewer
|
||||||
|
|
||||||
|
The Logs viewer is found in the TOOLS section of the dashboard.
|
||||||
|
|
||||||
|
**What you should see:**
|
||||||
|
- Dashboard with navigation sections: STATUS, TOOLS, ADMIN
|
||||||
|
- Various cards and widgets in the main area
|
||||||
|
|
||||||
|
Look for a "Logs" button (📋 Logs) in the TOOLS section of the dashboard. Note that some menu items may require clicking on the section header (like "TOOLS") to expand the menu first.
|
||||||
|
|
||||||
|
Screenshot: `screenshots/04-viewing-logs/light/01-dashboard.png`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2: The Logs Modal
|
||||||
|
|
||||||
|
Click on **Logs** to open the logs viewer modal.
|
||||||
|
|
||||||
|
**What you should see:**
|
||||||
|
- **Title:** DNS Logs (or relevant service name)
|
||||||
|
- **Show dropdown:** Options to show 25, 50, 100, or 200 log lines
|
||||||
|
- **Live/Controls:** Live streaming toggle (📡 Live) and pause button (⏸️ Pause)
|
||||||
|
- **Close button:** ✕ in the top right
|
||||||
|
- **Log content area:** Shows the actual log entries
|
||||||
|
|
||||||
|
Screenshot: `screenshots/04-viewing-logs/light/02-logs-modal-open.png`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3: Understanding the Log Display
|
||||||
|
|
||||||
|
The log viewer shows:
|
||||||
|
- **Timestamp** — When each log entry was created
|
||||||
|
- **Service** — Which service generated the entry
|
||||||
|
- **Message** — The actual log content
|
||||||
|
|
||||||
|
**Log Line Options:**
|
||||||
|
Use the "Show" dropdown to control how many log lines are displayed at once:
|
||||||
|
- 25 lines — For quick overview
|
||||||
|
- 50 lines — Default view
|
||||||
|
- 100 lines — For more context
|
||||||
|
- 200 lines — For detailed analysis
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4: Live Log Streaming
|
||||||
|
|
||||||
|
DashCaddy supports real-time log streaming:
|
||||||
|
|
||||||
|
**To enable live streaming:**
|
||||||
|
- Click the **📡 Live** button to start streaming logs in real-time
|
||||||
|
- New log entries will appear automatically at the top or bottom of the list
|
||||||
|
|
||||||
|
**To pause streaming:**
|
||||||
|
- Click **⏸️ Pause** to stop auto-updating
|
||||||
|
- This lets you scroll through logs without new entries pushing content
|
||||||
|
|
||||||
|
**What you should see:**
|
||||||
|
When live streaming is active, new log entries appear automatically. The "Loading logs..." message will be replaced with actual log data if logs are available from your services.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 5: Filtering and Navigation
|
||||||
|
|
||||||
|
The log viewer supports keyboard navigation:
|
||||||
|
- Use **↑/↓ arrow keys** to navigate through log entries
|
||||||
|
- **Enter** to select an entry (if applicable)
|
||||||
|
- **Esc** to close the modal
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Issues
|
||||||
|
|
||||||
|
| Issue | Solution |
|
||||||
|
|-------|----------|
|
||||||
|
| Logs show "Loading logs..." | No services are running, or services haven't generated logs yet |
|
||||||
|
| Can't find Logs button | The button may be in a collapsed menu — click on "TOOLS" section header to expand |
|
||||||
|
| Log entries not appearing | Live streaming may be paused — click 📡 Live to resume |
|
||||||
|
| Too many logs | Use the "Show" dropdown to limit displayed lines |
|
||||||
|
| Can't scroll through logs | Make sure pause (⏸️ Pause) is enabled to prevent new entries from pushing content |
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# Using Quick Search
|
||||||
|
|
||||||
|
DashCaddy's Quick Search feature provides instant access to services, settings, and apps across your dashboard. This tutorial shows how to use it.
|
||||||
|
|
||||||
|
**Prerequisites:** Logged into DashCaddy dashboard.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1: Open Quick Search
|
||||||
|
|
||||||
|
Quick Search is accessed via a modal that can be triggered in two ways:
|
||||||
|
|
||||||
|
**Method 1: Keyboard shortcut**
|
||||||
|
- Press **Ctrl+K** (or **Cmd+K** on Mac) to open Quick Search
|
||||||
|
|
||||||
|
**Method 2: Via the UI**
|
||||||
|
- The Quick Search modal can be accessed from the dashboard header or menu
|
||||||
|
|
||||||
|
Screenshot: `screenshots/05-quick-search/light/01-dashboard.png`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2: The Quick Search Modal
|
||||||
|
|
||||||
|
When opened, the Quick Search modal appears centered on your screen with a search input field.
|
||||||
|
|
||||||
|
**What you should see:**
|
||||||
|
- Search input field at the top
|
||||||
|
- Keyboard shortcut hints below the input:
|
||||||
|
- **ESC** — Close the modal
|
||||||
|
- **↑↓ Navigate** — Move between search results
|
||||||
|
- **Enter Select** — Select the highlighted result
|
||||||
|
- **Esc Close** — Another reminder to close with Escape
|
||||||
|
|
||||||
|
Screenshot: `screenshots/05-quick-search/light/02-quick-search-open.png`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3: Search for Services or Settings
|
||||||
|
|
||||||
|
Click on the search input field and start typing. Quick Search supports fuzzy matching and will show results as you type.
|
||||||
|
|
||||||
|
**What to search for:**
|
||||||
|
- Service names (e.g., "Plex", "Gitea", "Nextcloud")
|
||||||
|
- Settings names (e.g., "backup", "theme", "notifications")
|
||||||
|
- Dashboard features (e.g., "logs", "stats", "docker")
|
||||||
|
|
||||||
|
Screenshot: `screenshots/05-quick-search/light/03-quick-search-typed.png`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4: Navigate and Select Results
|
||||||
|
|
||||||
|
Use the keyboard to navigate through search results:
|
||||||
|
|
||||||
|
1. **↑/↓ arrows** — Move the selection highlight up or down through results
|
||||||
|
2. **Enter** — Select and open the highlighted item
|
||||||
|
3. **ESC** — Close the Quick Search modal without selecting anything
|
||||||
|
|
||||||
|
**Result types may include:**
|
||||||
|
- Services (apps you've deployed)
|
||||||
|
- Settings pages
|
||||||
|
- Dashboard features and modals
|
||||||
|
- Documentation links
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 5: Quick Actions
|
||||||
|
|
||||||
|
Depending on the selected result, Quick Search can:
|
||||||
|
- Open a service's management modal
|
||||||
|
- Navigate to a settings page
|
||||||
|
- Open a specific dashboard feature
|
||||||
|
- Show detailed information about a service
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Issues
|
||||||
|
|
||||||
|
| Issue | Solution |
|
||||||
|
|-------|----------|
|
||||||
|
| Quick Search doesn't open | Try the keyboard shortcut (Ctrl+K/Cmd+K) |
|
||||||
|
| No results appearing | Try shorter search terms; check spelling |
|
||||||
|
| Results not relevant | Quick Search may need services to be configured first |
|
||||||
|
| Can't select with Enter | Make sure a result is highlighted (has focus) |
|
||||||
|
| Modal won't close | Press ESC to close; if it doesn't work, click outside the modal |
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
# Backup & Restore Your Configuration
|
||||||
|
|
||||||
|
DashCaddy includes a comprehensive backup system that saves your entire configuration — services, credentials, themes, and settings — into a single file. This tutorial shows how to create backups and restore from them.
|
||||||
|
|
||||||
|
**Prerequisites:** Logged into DashCaddy dashboard.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1: Access the Backup Modal
|
||||||
|
|
||||||
|
The Backup feature is found in the TOOLS section of the dashboard.
|
||||||
|
|
||||||
|
**What you should see:**
|
||||||
|
- Dashboard with STATUS, TOOLS, ADMIN navigation
|
||||||
|
- Service cards and widgets
|
||||||
|
|
||||||
|
Navigate to the TOOLS section and click on **Backup** to open the Backup & Restore modal.
|
||||||
|
|
||||||
|
Screenshot: `screenshots/06-backup-restore/light/01-dashboard.png`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2: The Backup Modal
|
||||||
|
|
||||||
|
The Backup modal opens with three tabs and detailed information about what gets backed up.
|
||||||
|
|
||||||
|
**What you should see:**
|
||||||
|
- **Title:** 💾 Backup & Restore
|
||||||
|
- **Description:** "Full backup of your entire DashCaddy setup — server config, credentials, themes, and browser preferences in one file."
|
||||||
|
- **Tab buttons:** Manual, Automated, History
|
||||||
|
|
||||||
|
Screenshot: `screenshots/06-backup-restore/light/02-backup-modal-open.png`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3: Manual Backup (Export)
|
||||||
|
|
||||||
|
The **Manual** tab is selected by default and contains export options.
|
||||||
|
|
||||||
|
**What you should see:**
|
||||||
|
- **📤 Export Backup** section with description:
|
||||||
|
- "Downloads everything — services, Caddyfile, credentials, encryption key, themes, and all browser preferences."
|
||||||
|
- **⬇️ Download Full Backup** button
|
||||||
|
|
||||||
|
**To create a manual backup:**
|
||||||
|
1. Click **⬇️ Download Full Backup**
|
||||||
|
2. Your browser will download a backup file (typically `.tar.gz` or similar archive)
|
||||||
|
3. Save this file in a safe location
|
||||||
|
|
||||||
|
The backup file includes:
|
||||||
|
- All service configurations
|
||||||
|
- Caddy reverse proxy configuration
|
||||||
|
- Encrypted credentials
|
||||||
|
- Encryption keys
|
||||||
|
- Custom themes
|
||||||
|
- Browser preferences and settings
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4: Restore from Backup
|
||||||
|
|
||||||
|
The **📥 Restore Backup** section lets you restore from a previously saved backup file.
|
||||||
|
|
||||||
|
**To restore from backup:**
|
||||||
|
1. Click the upload/restore button (may be labeled differently in your version)
|
||||||
|
2. Select your backup file from your computer
|
||||||
|
3. Confirm the restore operation when prompted
|
||||||
|
4. Wait for the restore process to complete
|
||||||
|
|
||||||
|
**⚠️ Warning:** Restoring a backup will replace your current configuration. Make sure to:
|
||||||
|
- Export your current configuration first (if needed)
|
||||||
|
- Verify the backup file is from a trusted source
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 5: Automated Backups
|
||||||
|
|
||||||
|
The **Automated** tab lets you schedule regular backups.
|
||||||
|
|
||||||
|
**What you might see:**
|
||||||
|
- Backup schedule configuration
|
||||||
|
- Options for backup frequency (daily, weekly, etc.)
|
||||||
|
- Backup retention settings
|
||||||
|
|
||||||
|
Configure automated backups to ensure you always have recent restore points available.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 6: Backup History
|
||||||
|
|
||||||
|
The **History** tab shows previous backup operations.
|
||||||
|
|
||||||
|
**What you might see:**
|
||||||
|
- List of past backups
|
||||||
|
- Timestamps for each backup
|
||||||
|
- Restore options for individual backups
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Issues
|
||||||
|
|
||||||
|
| Issue | Solution |
|
||||||
|
|-------|----------|
|
||||||
|
| Download button not working | Check browser popup blocker; try a different browser |
|
||||||
|
| Backup file won't upload | Ensure you're uploading the correct file format; check file size limits |
|
||||||
|
| Restore fails | Verify the backup file is complete and not corrupted |
|
||||||
|
| Automated backup not running | Check that the DashCaddy service is running; verify schedule settings |
|
||||||
|
| Forgotten encryption key | Without the encryption key from backup, some credentials may not restore |
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
# Monitoring Resources with Stats
|
||||||
|
|
||||||
|
DashCaddy's Stats feature provides real-time and historical monitoring of CPU, memory, network, and disk usage for your containers. This tutorial shows how to access and interpret the monitoring data.
|
||||||
|
|
||||||
|
**Prerequisites:** Logged into DashCaddy dashboard with Docker containers running.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1: Access the Stats Modal
|
||||||
|
|
||||||
|
The Stats monitoring is found in the STATUS section of the dashboard.
|
||||||
|
|
||||||
|
**What you should see:**
|
||||||
|
- Dashboard with STATUS, TOOLS, ADMIN navigation
|
||||||
|
- Various service cards and status indicators
|
||||||
|
|
||||||
|
Navigate to the STATUS section and click on **Stats** to open the Resource Monitor.
|
||||||
|
|
||||||
|
Screenshot: `screenshots/07-stats-monitoring/light/01-dashboard.png`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2: The Stats Modal
|
||||||
|
|
||||||
|
The Stats modal opens with resource monitoring data organized into tabs.
|
||||||
|
|
||||||
|
**What you should see:**
|
||||||
|
- **Title:** 📊 Resource Monitor
|
||||||
|
- **Description:** "Real-time and historical CPU, memory, network, and disk usage for containers."
|
||||||
|
- **Tabs:** Live Stats, 24h Summary, Alerts
|
||||||
|
|
||||||
|
Screenshot: `screenshots/07-stats-monitoring/light/02-stats-modal-open.png`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3: Live Stats View
|
||||||
|
|
||||||
|
The **Live Stats** tab shows real-time resource usage.
|
||||||
|
|
||||||
|
**What you might see:**
|
||||||
|
- Container list with real-time metrics
|
||||||
|
- CPU usage percentage per container
|
||||||
|
- Memory usage (used/available)
|
||||||
|
- Network I/O rates
|
||||||
|
- Disk I/O rates
|
||||||
|
- Auto-refresh indicator: "Auto-refresh every 5s"
|
||||||
|
|
||||||
|
**Refresh controls:**
|
||||||
|
- **🔄 Refresh Now** — Manually trigger a refresh of all stats
|
||||||
|
- Automatic refresh can be paused if needed
|
||||||
|
|
||||||
|
Screenshot: `screenshots/07-stats-monitoring/light/02-stats-modal-open.png`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4: 24h Summary View
|
||||||
|
|
||||||
|
The **24h Summary** tab shows aggregated statistics over the past 24 hours.
|
||||||
|
|
||||||
|
**What you might see:**
|
||||||
|
- Peak CPU usage
|
||||||
|
- Average memory usage
|
||||||
|
- Total network traffic
|
||||||
|
- Disk usage trends
|
||||||
|
- Historical graphs if available
|
||||||
|
|
||||||
|
This view helps identify resource usage patterns and plan for capacity needs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 5: Alerts View
|
||||||
|
|
||||||
|
The **Alerts** tab shows any resource-related alerts.
|
||||||
|
|
||||||
|
**What you might see:**
|
||||||
|
- High CPU warnings
|
||||||
|
- Memory pressure alerts
|
||||||
|
- Disk space warnings
|
||||||
|
- Network issues
|
||||||
|
|
||||||
|
Alerts help you stay on top of problems before they cause service disruptions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Understanding the Metrics
|
||||||
|
|
||||||
|
**CPU Usage:**
|
||||||
|
- Shows percentage of CPU being used by each container
|
||||||
|
- High CPU (>80%) sustained may indicate performance issues
|
||||||
|
|
||||||
|
**Memory Usage:**
|
||||||
|
- Shows RAM used vs. available
|
||||||
|
- Watch for containers with memory limits approaching capacity
|
||||||
|
|
||||||
|
**Network I/O:**
|
||||||
|
- Bytes/packets in and out
|
||||||
|
- Helps identify unexpected network activity
|
||||||
|
|
||||||
|
**Disk I/O:**
|
||||||
|
- Read/write speeds
|
||||||
|
- Important for storage-heavy workloads
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Issues
|
||||||
|
|
||||||
|
| Issue | Solution |
|
||||||
|
|-------|----------|
|
||||||
|
| Stats show "Loading container stats..." | No containers running, or Docker is not responding |
|
||||||
|
| Auto-refresh not working | Click "🔄 Refresh Now" manually; check Docker daemon |
|
||||||
|
| Missing container in stats | Container may have stopped; check container status |
|
||||||
|
| Network stats show zeros | Network monitoring may not be enabled for that container |
|
||||||
|
| High CPU alerts | Check which container is using resources; consider resource limits |
|
||||||