# AI Agent
Source: https://docs.tinykit.studio/agent
Prompting guide for the tinykit AI Agent.
The AI Agent acts as an interface to the tinykit codebase. It interprets natural language prompts to modify app code, manage configurations, and handle database interactions.
## Mechanics
When a prompt is submitted:
1. **Context Loading**: The agent ingests the current `Code`, design system states, content fields, and database collections.
2. **Tool selection**: It determines which tools to execute (e.g., `write_code`, `create_collection`).
3. **Execution**: Changes are applied to the project.
4. **Refresh**: The preview pane updates.
## Prompting Strategy
Describe **what** the feature should do (behavior), rather than **how** to write the code (implementation).
* **Vague**: "Make the list better."
* **Implementation-focused**: "Create a loop using `.map()` to render `li` elements with class `item`."
* **Behavior-focused**: "Display a list of episodes where each item links to its details page."
## Tools
The agent has access to the following server-side tools:
| Tool | Function |
| :--------------------- | :--------------------------------- |
| `write_code` | Overwrites app code with new code. |
| `create_content_field` | Defines editable content fields. |
| `create_design_field` | Defines CSS variables. |
| `create_data_file` | Creates data collections. |
| `insert_records` | Seeds data into collections. |
| `update_spec` | Updates project metadata. |
## Limitations
* **Single File**: Modifications are restricted to a single `Svelte` component file.
* **CSS**: Uses semantic classes and CSS variables, not utility frameworks like Tailwind (though you can use them by prompting the agent or manually including them).
* **Runtime**: Cannot execute arbitrary shell commands or write backend logic besides `proxy` (yet).
* **Database**: At the moment, collections under the 'Data' are all stored within a single Pocketbase JSON field. In a future update, these will be replaced with actual Pocketbase collections.
# Architecture
Source: https://docs.tinykit.studio/architecture
How tinykit works under the hood
## One Server Philosophy
**One server. One deployment. Zero complexity.**
Unlike other platforms where you build locally and deploy separately, tinykit runs the builder AND your app on the same server. Edit at `/tinykit`, ship at `/`.
***
## URL Structure
Your tinykit instance serves everything from a single domain:
```
tinykit Server (Railway)
├── /tinykit/dashboard → Project list (you)
├── /tinykit/studio → Edit current domain's app (you)
├── / → Your generated app (users)
├── /api/agent → AI endpoints
├── /api/projects → Project operations
└── /_pb/_ → PocketBase admin
```
| Path | Purpose | Who Uses It |
| ---------------------- | --------------------------- | --------------- |
| `/` | Your generated app | End users |
| `/tinykit/dashboard` | List all projects | You (developer) |
| `/tinykit/studio` | Edit app for current domain | You (developer) |
| `/tinykit/studio?id=X` | Edit specific project | You (developer) |
| `/api/agent` | AI code generation | Builder |
| `/_pb/_` | PocketBase admin | You (optional) |
***
## Domain-Based Routing
Run hundreds of apps from one tinykit instance. Each domain serves a different app:
```
calculator.myserver.com/ → Serves calculator app
calculator.myserver.com/tinykit → Edit calculator app
blog.myserver.com/ → Serves blog app
blog.myserver.com/tinykit → Edit blog app
recipes.myserver.com/ → Serves recipes app
recipes.myserver.com/tinykit → Edit recipes app
```
**How it works:**
1. Point multiple domains to your tinykit server
2. Each domain is associated with a project in PocketBase
3. Root URL (`/`) serves the pre-built HTML for that domain's project
4. `/tinykit` lets you edit the project for the current domain
**Unknown domains** redirect to `/tinykit/new?domain=X` to create a new project.
***
## Tech Stack
Fast, modern framework for the builder and generated apps
Modern code editor with syntax highlighting and autocomplete
Your choice of AI provider for code generation
Embedded database for data persistence
Utility-first styling for rapid UI development
Type safety throughout the codebase
***
## Data Storage
All project data is stored in a single PocketBase collection (`_tk_projects`):
```
_tk_projects
├── frontend_code → Code (your app's code)
├── content → Content fields (JSON)
├── design → CSS variables (JSON)
├── agent_chat → Conversation history (JSON)
├── snapshots → Time travel history (JSON)
├── data → App data collections (JSON)
├── domain → Associated domain
└── published_html → Compiled HTML (production build)
```
Everything in one collection means simple backups and easy migrations.
***
## Data Flow
Send a prompt like "Create a todo list app" to the AI Agent panel.
The AI responds with code, which streams to your browser in real-time.
Generated code is saved to the project's `frontend_code` field.
The live preview compiles and shows your changes immediately.
Server compiles Svelte to standalone HTML, saves as static file.
Anyone visiting your root URL sees the updated app instantly.
***
## Build System
**Preview** uses in-browser Svelte compilation for instant feedback.
**Production** uses server-side compilation:
* Svelte 5's native `compile()` function
* Generates standalone HTML with CDN-based imports
* Triggered by clicking "Deploy" button
* Result stored as file attachment in PocketBase
***
## AI Agent System
The AI agent uses a **tool-use pattern** to build your app:
```
You: "Add a contact form"
│
▼
┌─────────────────────────────┐
│ LLM receives: │
│ - Your prompt │
│ - Current code │
│ - Design/content fields │
│ - Conversation history │
└─────────────────────────────┘
│
▼
┌─────────────────────────────┐
│ LLM calls tools: │
│ - write_code │
│ - create_design_field │
│ - create_content_field │
│ - create_data_file │
└─────────────────────────────┘
│
▼
┌─────────────────────────────┐
│ Tools execute │
│ Results fed back │
│ Loop until done │
└─────────────────────────────┘
│
▼
Response streamed to you
```
### Available Tools
| Tool | What It Does |
| ---------------------- | ---------------------------------- |
| `write_code` | Updates app code |
| `create_design_field` | Adds CSS variables (colors, fonts) |
| `create_content_field` | Adds CMS fields (text, images) |
| `create_data_file` | Creates data collections |
| `insert_records` | Seeds data into collections |
| `update_spec` | Updates project metadata |
***
## Performance
| Operation | Typical Time |
| ---------------- | ------------------------- |
| Editor load | \~100-200ms |
| File save | \~50-100ms |
| Preview refresh | \~100-200ms |
| AI response | \~2-5s (depends on model) |
| Production build | \~500ms-2s |
***
## Deployment Options
While Railway is recommended for simplicity, you can deploy anywhere that runs Node.js:
One-click deploy with the template. Automatic HTTPS, custom domains, and scaling.
[](https://railway.com/deploy/tinykit?referralCode=RCPU7k\&utm_medium=integration\&utm_source=template\&utm_campaign=generic)
```bash theme={null}
fly launch
fly secrets set LLM_API_KEY=sk-...
fly deploy
```
Connect your GitHub repo and configure environment variables in the dashboard.
```bash theme={null}
git clone https://github.com/tinykit-studio/tinykit.git
cd tinykit
npm install
npm run build
npm run preview
```
Use PM2 or systemd to keep it running.
```dockerfile theme={null}
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm install && npm run build
CMD ["npm", "run", "preview"]
```
# Configuration
Source: https://docs.tinykit.studio/configuration
Environment variables and settings reference
Configure tinykit using environment variables. All settings can be configured via a `.env` file or passed directly to your deployment platform.
## Quick Reference
| Variable | Required | Default | Description |
| --------------------------- | -------- | --------- | ------------------------- |
| `LLM_PROVIDER` | No\* | - | AI provider to use |
| `LLM_API_KEY` | No\* | - | API key for your provider |
| `LLM_MODEL` | No | varies | Model name |
| `LLM_BASE_URL` | No | - | Custom API endpoint |
| `POCKETBASE_ADMIN_EMAIL` | Yes | - | Database admin email |
| `POCKETBASE_ADMIN_PASSWORD` | Yes | - | Database admin password |
| `PORT` | No | `3000` | Server port |
| `HOST` | No | `0.0.0.0` | Server host |
\*Can also be configured via `/tinykit/settings` UI after deployment.
***
## AI Provider Settings
### LLM\_PROVIDER
The AI provider to use for the agent. Options:
| Provider | Value | Description |
| --------- | ----------- | --------------------------- |
| Anthropic | `anthropic` | Claude models (recommended) |
| OpenAI | `openai` | GPT-4 and GPT-3.5 models |
| Google | `gemini` | Gemini models |
```bash theme={null}
LLM_PROVIDER=anthropic
```
### LLM\_API\_KEY
Your API key from the provider. Get yours from:
* **Anthropic**: [console.anthropic.com](https://console.anthropic.com/)
* **OpenAI**: [platform.openai.com/api-keys](https://platform.openai.com/api-keys)
* **Google**: [aistudio.google.com/apikey](https://aistudio.google.com/apikey)
```bash theme={null}
LLM_API_KEY=sk-ant-api03-...
```
Never commit API keys to version control. Use environment variables or secrets management.
### LLM\_MODEL
The specific model to use. Defaults vary by provider:
| Provider | Default | Recommended |
| --------- | -------------------------- | -------------------------- |
| Anthropic | `claude-sonnet-4-20250514` | `claude-sonnet-4-20250514` |
| OpenAI | `gpt-4o` | `gpt-4o` |
| Gemini | `gemini-2.0-flash` | `gemini-2.0-flash` |
```bash theme={null}
LLM_MODEL=claude-sonnet-4-20250514
```
### LLM\_BASE\_URL
Custom API endpoint for OpenAI-compatible providers. Only needed for:
* Self-hosted models
* Other OpenAI-compatible APIs
```bash theme={null}
# For local OpenAI-compatible server
LLM_BASE_URL=http://localhost:8080/v1
```
***
## Database Settings
### POCKETBASE\_ADMIN\_EMAIL
Email address for the Pocketbase admin account. Used to initialize the database on first run.
```bash theme={null}
POCKETBASE_ADMIN_EMAIL=admin@yourdomain.com
```
### POCKETBASE\_ADMIN\_PASSWORD
Password for the Pocketbase admin account. Must be at least 8 characters.
```bash theme={null}
POCKETBASE_ADMIN_PASSWORD=your-secure-password
```
Use a strong, unique password. This account has full access to your database.
### POCKETBASE\_URL
Optional. URL of an external Pocketbase instance. By default, tinykit runs its own embedded Pocketbase.
```bash theme={null}
# Only set if using external Pocketbase
POCKETBASE_URL=http://127.0.0.1:8091
```
***
## Server Settings
### PORT
The port the server listens on.
```bash theme={null}
PORT=3000
```
* **Default**: `3000` (production), `5173` (development)
* Railway and similar platforms set this automatically
### HOST
The host address to bind to.
```bash theme={null}
HOST=0.0.0.0
```
* **Default**: `0.0.0.0` (all interfaces)
* Use `127.0.0.1` to restrict to localhost only
***
## Provider Examples
### Anthropic (Claude)
```bash theme={null}
LLM_PROVIDER=anthropic
LLM_API_KEY=sk-ant-api03-...
LLM_MODEL=claude-sonnet-4-20250514
```
### OpenAI (GPT-4)
```bash theme={null}
LLM_PROVIDER=openai
LLM_API_KEY=sk-...
LLM_MODEL=gpt-4
```
### Google (Gemini)
```bash theme={null}
LLM_PROVIDER=gemini
LLM_API_KEY=your-gemini-api-key
LLM_MODEL=gemini-pro
```
***
## Complete Example
Here's a complete `.env` file for production:
```bash theme={null}
# AI Configuration
LLM_PROVIDER=anthropic
LLM_API_KEY=sk-ant-api03-...
LLM_MODEL=claude-sonnet-4-20250514
# Database
POCKETBASE_ADMIN_EMAIL=admin@yourdomain.com
POCKETBASE_ADMIN_PASSWORD=your-secure-password-here
# Server (usually set by platform)
PORT=3000
HOST=0.0.0.0
```
***
## Platform-Specific Notes
### Railway
Railway automatically sets `PORT`. Configure other variables in the Railway dashboard under **Variables**.
### Docker
Pass variables with `-e` flags or use `--env-file`:
```bash theme={null}
docker run -d \
-e LLM_PROVIDER=anthropic \
-e LLM_API_KEY=sk-ant-... \
-e POCKETBASE_ADMIN_EMAIL=admin@example.com \
-e POCKETBASE_ADMIN_PASSWORD=password \
tinykit
```
### VPS / PM2
Use a `.env` file in the project root, or configure in `ecosystem.config.js`:
```javascript theme={null}
module.exports = {
apps: [{
name: 'tinykit',
script: './start.sh',
env: {
LLM_PROVIDER: 'anthropic',
LLM_API_KEY: 'sk-ant-...',
// ...
}
}]
}
```
***
## Troubleshooting
Check these in order:
1. `LLM_API_KEY` is set and valid
2. `LLM_PROVIDER` matches your key (e.g., Anthropic key with `anthropic` provider)
3. Your account has credits/quota remaining
4. Check logs for specific error messages
Verify:
1. `POCKETBASE_ADMIN_EMAIL` is a valid email format
2. `POCKETBASE_ADMIN_PASSWORD` is at least 8 characters
3. The `pocketbase/pb_data` directory is writable
Check:
1. `PORT` isn't already in use (`lsof -i :3000`)
2. `HOST` is set correctly for your environment
3. Firewall allows traffic on the port
Environment variables are read at startup. After changing `.env`:
* **Development**: Restart `npm run dev`
* **PM2**: Run `pm2 restart tinykit`
* **Docker**: Recreate the container
* **systemd**: Run `sudo systemctl restart tinykit`
# Content Fields
Source: https://docs.tinykit.studio/content-fields
CMS-like editable text for non-developers
Content fields let non-technical users edit text without touching code. Headlines, descriptions, button labels—anything that might need updating can be a content field.
## How It Works
1. The AI (or you) creates a content field with a name and value
2. The field appears in the **Content tab** with an editable input
3. Your code references the field via `import content from '$content'`
4. When someone edits the field, the preview updates instantly
```
Content Tab Your Code
┌─────────────────────┐ ┌─────────────────────┐
│ Hero Title │ │
│
└─────────────────────┘ └─────────────────────┘
```
***
## Using Content Fields
### In Your Code
```svelte theme={null}
{content.hero_title}
{content.hero_description}
```
### Name Conversion
Field names are converted to snake\_case:
| Field Name | Access As |
| ---------------- | -------------------------- |
| Hero Title | `content.hero_title` |
| CTA Button Text | `content.cta_button_text` |
| Footer Copyright | `content.footer_copyright` |
| App Name | `content.app_name` |
When you hover over a field in the Content tab, you'll see the exact key to use in your code.
### Default Values
Always provide fallbacks for robustness:
```svelte theme={null}
{content.hero_title || 'Welcome'}
{content.tagline || 'Your default tagline here'}
```
***
## Field Types
| Type | Input | Best For |
| ------------ | ---------------- | --------------------------- |
| **text** | Single line | Titles, labels, short text |
| **textarea** | Multi-line | Descriptions, paragraphs |
| **number** | Numeric input | Prices, quantities, limits |
| **boolean** | Toggle switch | Feature flags, visibility |
| **image** | Image upload | Logos, photos, icons |
| **markdown** | Rich text editor | Formatted content, articles |
### Text Fields
Single line text for titles, labels, and short content:
```svelte theme={null}
{content.page_title}
```
### Textarea Fields
Multi-line text for longer content:
```svelte theme={null}
{content.about_text}
{content.author_bio}
```
### Number Fields
Numeric values that can be used in calculations:
```svelte theme={null}
{/if}
{#if content.enable_dark_mode}
...
{/if}
```
### Image Fields
Image uploads with automatic URL handling:
```svelte theme={null}
```
Image fields automatically convert uploaded filenames to full asset URLs.
### Markdown Fields
Rich text content with markdown formatting:
```svelte theme={null}
{@html content.article_body}
```
Markdown fields are rendered as HTML. Only use `{@html}` with trusted content to avoid XSS risks.
***
## Creating Content Fields
### Via the AI
Ask the AI to create content fields:
```
Add a hero section with a title and description that I can edit without code
```
The AI will:
1. Create content fields for the text
2. Reference them in the code
3. Fields appear in the Content tab
### Via the Content Tab
1. Click **Add Content Field** at the bottom of the Content tab
2. Enter a name (e.g., "Hero Title")
3. Choose a type
4. Set an initial value
5. Optionally add a description
6. Click **Add Field**
### In Code (Advanced)
If you're editing code manually, first use the text in your code:
```svelte theme={null}
{content.new_headline}
```
Then ask the AI to create the matching field, or add it in the Content tab.
***
## Best Practices
### Use Descriptive Names
* Hero Title
* Navigation Home Link
* Footer Copyright Year
* Empty Cart Message
* Title
* Text 1
* String
* Foo
### Group Related Content
Use consistent prefixes for related fields:
```
hero_title
hero_description
hero_cta_text
footer_copyright
footer_company_name
footer_email
```
### Provide Context with Descriptions
When creating fields, add descriptions to help editors:
```
Name: Hero Title
Description: Main headline on the home page (keep under 50 characters)
Name: CTA Button
Description: Call-to-action button text (e.g., "Get Started", "Try Free")
```
### Handle Missing Values
Always provide fallbacks in case a field hasn't been created yet:
```svelte theme={null}
{/if}
```
### Lists from JSON
```svelte theme={null}
```
### Dynamic Placeholders
```svelte theme={null}
```
### Content with Design
Combine content fields with design fields:
```svelte theme={null}
{content.hero_title}
```
***
## Sharing the Content Tab
Content fields are designed for non-developers to edit. You can share access to just the Content tab without exposing the full builder:
A dedicated content-only editing view is planned for a future release. For now, editors access the full builder but only need to use the Content tab.
Consider creating a guide for your content editors:
1. Log in at `yourapp.com/tinykit`
2. Click the **Content** tab
3. Edit values and see changes in the preview
4. Changes save automatically
***
## FAQ
Not directly in the UI. Delete the old field and create a new one with the correct name. Remember to update your code to use the new key.
The code will show `undefined` or your fallback value. Always use fallbacks like `{content.title || 'Default'}`.
Content fields are plain text by default. For HTML, use `{@html content.rich_text}`, but be careful with user-provided content (XSS risk).
tinykit doesn't enforce required fields. Use fallback values in your code to handle missing content gracefully.
# Data & Content
Source: https://docs.tinykit.studio/data-fetching
Working with data, content fields, and external APIs in tinykit
tinykit provides three special imports for handling data in your apps:
| Import | Purpose |
| ---------- | ------------------------------------------ |
| `$data` | Database collections with realtime updates |
| `$content` | Editable CMS text fields |
| `$tinykit` | Proxy for fetching external APIs |
***
## Database (\$data)
Store and retrieve data with realtime subscriptions. Data is stored in Pocketbase and syncs automatically across all connected clients.
### Basic Usage
```svelte theme={null}
{#if loading}
Loading...
{:else}
{#each todos as todo (todo.id)}
{todo.title}
{/each}
{/if}
```
### CRUD Operations
```javascript theme={null}
// Create a new record
await data.todos.create({
title: 'Buy groceries',
completed: false
})
// Update a record by ID
await data.todos.update('abc123', {
completed: true
})
// Delete a record by ID
await data.todos.delete('abc123')
```
All operations trigger realtime updates—any subscribed component will update automatically.
### Complete Example
```svelte theme={null}
{#each todos as todo (todo.id)}
toggle_todo(todo)}
/>
{todo.title}
{/each}
```
### Creating Collections
The AI creates collections when you ask for data storage. You can also create them in the Data tab or ask the AI directly:
```
Add a collection for storing recipes with title, ingredients, and instructions
```
***
## Content Fields (\$content)
Editable text values that non-developers can change without touching code. Perfect for headlines, descriptions, and labels.
### Basic Usage
```svelte theme={null}
{content.hero_title}
{content.hero_description}
```
Content field names are automatically converted to snake\_case:
* "Hero Title" → `content.hero_title`
* "CTA Button Text" → `content.cta_button_text`
### Default Values
Always provide fallbacks for robustness:
```svelte theme={null}
{content.hero_title || 'Welcome'}
```
### Field Types
| Type | Description | Example Value |
| ---------- | ---------------------------- | --------------------------- |
| `text` | Single line text | `"Welcome to My App"` |
| `textarea` | Multi-line text | `"A longer description..."` |
| `number` | Numeric value | `42` |
| `boolean` | True/false toggle | `true` |
| `image` | Image upload | `"/path/to/image.png"` |
| `markdown` | Rich text (rendered as HTML) | `"# Heading\n\nParagraph"` |
### Creating Content Fields
The AI creates content fields when it builds your app. You can also:
1. **Ask the AI**: "Add a content field for the footer copyright text"
2. **Use the Content tab**: Add fields directly in the builder
3. **Edit values**: Change text in the Content tab without touching code
***
## External APIs (\$tinykit)
Fetch data from external APIs without CORS issues. The proxy routes requests through your server.
### Fetching JSON
```svelte theme={null}
```
### Fetching Text (RSS, HTML, XML)
```svelte theme={null}
```
### Media URLs
For audio, images, and other media that need a direct URL:
```svelte theme={null}
```
### Raw Fetch
For full control over the request:
```javascript theme={null}
import { proxy } from '$tinykit'
const response = await proxy('https://api.example.com/data')
const json = await response.json()
```
### When to Use Proxy
Use the proxy when fetching from external domains that would block direct browser requests:
* RSS feeds
* External APIs without CORS headers
* Scraping web pages
* Remote media files (audio/video)
* Your own `$data` collections
* APIs with proper CORS headers
* CDN resources (images, scripts)
* Same-origin requests
***
## Common Patterns
### Loading States
Always show loading indicators:
```svelte theme={null}
{#if loading}
Loading...
{:else if items.length === 0}
No items yet
{:else}
{#each items as item (item.id)}
{item.name}
{/each}
{/if}
```
### Filtering and Sorting
Use `$derived.by()` for computed lists:
```svelte theme={null}
```
Common mistake: `$derived(todos.filter(...))` won't work. Use `$derived.by(() => todos.filter(...))` for callbacks.
### Combining Data and Content
```svelte theme={null}
{:else}
{/if}
```
***
## Design Fields (CSS Variables)
While not an import, design fields work similarly—the AI creates CSS variables that you reference in your styles:
```css theme={null}
.card {
background: var(--card-background, #ffffff);
border-radius: var(--card-radius, 8px);
color: var(--body-text-color, #333333);
}
```
Always include fallback values: `var(--name, fallback)`. This ensures your app works even if a design field hasn't been created yet.
Design fields appear in the Design tab where you can adjust colors, fonts, spacing, and more with visual editors.
# Database
Source: https://docs.tinykit.studio/database
Store and manage data with Pocketbase
tinykit uses **Pocketbase**, an embedded SQLite database that runs alongside your app. Create collections, store records, and get realtime updates—all without setting up external infrastructure.
## How It Works
1. The AI (or you) creates a **collection** (like a database table)
2. Collections store **records** (rows of data)
3. Your code accesses data via `import data from '$data'`
4. Changes sync in realtime across all connected clients
5. The **Data tab** lets you browse and edit records visually
***
## Data Tab Overview
The Data tab shows all your collections:
**Features:**
* View all collections on the left
* Browse records for selected collection
* Add, edit, and delete records
* Create new collections with custom schemas
* Export data as JSON
***
## Accessing Data in Code
### Import and Subscribe
```svelte theme={null}
```
### CRUD Operations
```javascript theme={null}
// Create a new record
await data.todos.create({
title: 'Buy groceries',
completed: false
})
// Update a record by ID
await data.todos.update('abc123', {
completed: true
})
// Delete a record by ID
await data.todos.delete('abc123')
```
All operations trigger realtime updates automatically.
***
## Column Types
When creating collections, choose appropriate column types:
| Type | Description | Example Values |
| ----------- | --------------------- | -------------------------- |
| **text** | Any string | `"Hello"`, `"abc123"` |
| **number** | Numeric values | `42`, `3.14`, `-10` |
| **boolean** | True/false | `true`, `false` |
| **date** | Date/time | `"2024-01-15T10:30:00"` |
| **file** | Single file upload | `"document.pdf"` |
| **files** | Multiple file uploads | `["img1.png", "img2.png"]` |
| **json** | Structured data | `{"key": "value"}` |
### Schema Inference
If you create records without defining a schema, tinykit infers types from the first record:
```javascript theme={null}
// Schema auto-inferred as: title (text), count (number), active (boolean)
await data.items.create({
title: 'Example',
count: 42,
active: true
})
```
***
## Creating Collections
### Via the AI
Ask the AI to create collections:
```
Store recipes with a title, ingredients list, and instructions
```
The AI will:
1. Create a `recipes` collection
2. Define the schema (title, ingredients, instructions)
3. Wire up the code to use the collection
### Via the Data Tab
1. Click **Add Collection**
2. Enter a name (lowercase, underscores allowed)
3. Add columns with names and types
4. Click **Create**
### Via Code
If you reference a collection that doesn't exist, you can create it by adding the first record:
```javascript theme={null}
// Creates 'notes' collection with inferred schema
await data.notes.create({
title: 'First Note',
content: 'Hello world',
created_at: new Date().toISOString()
})
```
***
## Common Patterns
### Loading State
Always show loading indicators:
```svelte theme={null}
{#if loading}
Loading...
{:else if items.length === 0}
No items yet
{:else}
{#each items as item (item.id)}
{item.name}
{/each}
{/if}
```
### Filtering Data
Use `$derived.by()` for filtered views:
```svelte theme={null}
```
### Sorting Data
Copy arrays before sorting (sort mutates):
```svelte theme={null}
```
### Form Submission
```svelte theme={null}
```
### Optimistic Updates
For snappier UI, update state before the server responds:
```svelte theme={null}
```
***
## Record IDs
Every record has a unique `id` field:
* Auto-generated if not provided (5 alphanumeric characters)
* Can be manually set when creating records
* Used for updates and deletes
```javascript theme={null}
// Auto-generated ID
await data.items.create({ name: 'Test' })
// → { id: 'x7k2m', name: 'Test' }
// Manual ID
await data.items.create({ id: 'custom', name: 'Test' })
// → { id: 'custom', name: 'Test' }
```
***
## Timestamps
Records include automatic timestamps:
| Field | Description |
| --------- | --------------------------------- |
| `created` | When the record was created |
| `updated` | When the record was last modified |
```svelte theme={null}
{#each items as item (item.id)}
{/each}
```
***
## Pocketbase Admin
For advanced database management, access Pocketbase directly:
```
yourapp.com/_pb/_
```
The Pocketbase admin UI lets you:
* Browse all collections
* Run SQL queries
* Manage authentication
* Configure collection rules
* Export/import data
tinykit stores app data in the `_tk_projects` collection. Your app's collections are stored within each project's `data` field.
***
## Data Export
### From the Data Tab
1. Select a collection
2. Click the download icon
3. Get JSON export of all records
### From Pocketbase Admin
Access `/_pb/_` for full database export capabilities.
***
## Limitations
Keep these limitations in mind:
**No relations (yet)**: Collections are independent. For related data, store IDs manually:
```javascript theme={null}
// Store author_id instead of nested author object
await data.posts.create({
title: 'My Post',
author_id: 'usr123'
})
```
**Shared database**: All apps on a tinykit instance share the same Pocketbase. Use unique collection names or prefixes:
```
recipes_items (recipes app)
blog_posts (blog app)
crm_contacts (crm app)
```
**No migrations**: Schema changes should be made carefully. Adding fields is safe; removing or renaming requires manual data migration.
***
## FAQ
Data is stored in Pocketbase's SQLite database at `pocketbase/pb_data/`. This persists across app restarts.
Yes. The SQLite database is a single file. Copy `pocketbase/pb_data/data.db` for a full backup. You can also export via the Pocketbase admin UI.
SQLite handles millions of records efficiently. For very large datasets, consider pagination in your queries.
Access the Pocketbase admin at `/_pb/_` to run SQL queries directly.
In the Data tab, select the collection and click the delete button. This removes all records permanently.
Last write wins. Pocketbase doesn't have conflict resolution—the most recent update overwrites previous values.
# Design System
Source: https://docs.tinykit.studio/design-system
CSS variables and visual theming
Design fields are CSS variables that control your app's appearance. Colors, fonts, spacing, shadows—all adjustable with visual editors, no code required.
## How It Works
1. The AI (or you) creates a design field with a name and value
2. The field gets a CSS variable (e.g., `--page-background`)
3. Your code uses the variable: `background: var(--page-background)`
4. The Design tab shows visual editors for each field type
5. Changes update the preview instantly
```
Design Tab Your CSS
┌─────────────────────┐ ┌─────────────────────┐
│ Page Background │ │ .page { │
│ ┌───┬───┬───┬───┐ │ → │ background: │
│ │ 🔵 │ 🔴 │ 🟢 │...│ │ │ var(--page- │
│ └───┴───┴───┴───┘ │ │ background); │
└─────────────────────┘ │ } │
└─────────────────────┘
```
***
## Field Types
Each type has a specialized visual editor:
| Type | Editor | Example Values |
| ---------- | ------------------------- | --------------------------- |
| **color** | Color palette picker | `#3b82f6`, `#ffffff` |
| **size** | Slider (0-96px) | `16px`, `24px` |
| **font** | Font picker (1000+ fonts) | `Inter`, `Playfair Display` |
| **radius** | Radius slider | `8px`, `9999px` (full) |
| **shadow** | Shadow presets | `0 4px 6px rgba(0,0,0,0.1)` |
| **text** | Plain text input | Any custom value |
***
## Using Design Fields
### In Your CSS
Always include fallback values:
```css theme={null}
.card {
background: var(--card-background, #ffffff);
border-radius: var(--card-radius, 8px);
box-shadow: var(--card-shadow, none);
}
.heading {
font-family: var(--heading-font, sans-serif);
color: var(--heading-color, #1a1a1a);
}
body {
font-family: var(--body-font, system-ui, sans-serif);
font-size: var(--body-font-size, 16px);
background: var(--page-background, #f5f5f5);
}
```
Always use fallback values: `var(--name, fallback)`. This ensures your app works even before design fields are created.
### Name to CSS Variable
Field names are converted to kebab-case CSS variables:
| Field Name | CSS Variable |
| ----------------------- | --------------------------- |
| Page Background | `--page-background` |
| Card Border Color | `--card-border-color` |
| Button Hover Background | `--button-hover-background` |
| Body Font | `--body-font` |
***
## Color Fields
Color fields use a palette picker with:
* Quick-select color chips
* Full color picker
* Hex input
* Theme colors (other colors in your project)
### Common Color Fields
```css theme={null}
/* Page colors */
--page-background: #f5f5f5;
--card-background: #ffffff;
/* Text colors */
--heading-color: #1a1a1a;
--body-text-color: #333333;
--secondary-text-color: #666666;
--muted-text-color: #999999;
/* UI colors */
--accent-color: #3b82f6;
--border-color: #e5e5e5;
--hover-background: #f0f0f0;
/* Semantic colors */
--success-color: #22c55e;
--warning-color: #f59e0b;
--danger-color: #ef4444;
```
### Using Colors
```css theme={null}
.header {
background: var(--header-background, #ffffff);
border-bottom: 1px solid var(--border-color, #e5e5e5);
}
.button {
background: var(--accent-color, #3b82f6);
color: #ffffff;
}
.button:hover {
background: var(--button-hover-background, #2563eb);
}
```
***
## Font Fields
Font fields include a picker with 1000+ fonts from [Bunny Fonts](https://fonts.bunny.net/):
### Popular Fonts (Quick Select)
| Font | Category |
| ---------------- | ---------- |
| Inter | Sans-serif |
| Roboto | Sans-serif |
| Open Sans | Sans-serif |
| Poppins | Sans-serif |
| Montserrat | Sans-serif |
| Playfair Display | Serif |
| Merriweather | Serif |
| JetBrains Mono | Monospace |
| Source Code Pro | Monospace |
### Using Fonts
```css theme={null}
body {
font-family: var(--body-font, system-ui, sans-serif);
}
h1, h2, h3 {
font-family: var(--heading-font, var(--body-font, sans-serif));
}
code, pre {
font-family: var(--code-font, ui-monospace, monospace);
}
```
Chain fallbacks for fonts: `var(--heading-font, var(--body-font, sans-serif))` uses the body font if no heading font is set.
***
## Size Fields
Size fields use a slider from 0-96px. Good for:
* Font sizes
* Spacing (padding, margins, gaps)
* Icon sizes
### Using Sizes
```css theme={null}
.container {
padding: var(--container-padding, 24px);
gap: var(--card-gap, 16px);
}
.heading {
font-size: var(--heading-size, 32px);
}
.body {
font-size: var(--body-size, 16px);
}
```
***
## Radius Fields
Radius fields control border-radius with a visual slider:
* `0px` = square corners
* `8px` = subtle rounding
* `16px` = noticeable rounding
* `9999px` = fully rounded (pill/circle)
### Using Radius
```css theme={null}
.card {
border-radius: var(--card-radius, 8px);
}
.button {
border-radius: var(--button-radius, 6px);
}
.avatar {
border-radius: var(--avatar-radius, 9999px); /* circle */
}
.input {
border-radius: var(--input-radius, 4px);
}
```
***
## Shadow Fields
Shadow fields offer preset options:
| Preset | Value |
| --------- | --------------------------------- |
| **None** | `none` |
| **SM** | `0 1px 2px rgba(0,0,0,0.05)` |
| **MD** | `0 4px 6px rgba(0,0,0,0.1)` |
| **LG** | `0 10px 15px rgba(0,0,0,0.1)` |
| **XL** | `0 20px 25px rgba(0,0,0,0.15)` |
| **Inner** | `inset 0 2px 4px rgba(0,0,0,0.1)` |
### Using Shadows
```css theme={null}
.card {
box-shadow: var(--card-shadow, 0 1px 2px rgba(0,0,0,0.05));
}
.modal {
box-shadow: var(--modal-shadow, 0 20px 25px rgba(0,0,0,0.15));
}
.input:focus {
box-shadow: var(--focus-shadow, 0 0 0 3px rgba(59,130,246,0.3));
}
```
***
## Creating Design Fields
### Via the AI
The AI automatically creates design fields when building your app:
```
Create a card component with customizable colors and rounded corners
```
The AI will:
1. Write CSS using variables with fallbacks
2. Create design fields for each variable
3. Fields appear in the Design tab
### Via the Design Tab
1. Click **Add Design Field** at the bottom
2. Choose a type (color, size, font, etc.)
3. Enter a name (e.g., "Card Background")
4. Set the initial value using the visual editor
5. Click **Add Field**
### Workflow: Design Then Code
If you create design fields first, then write CSS:
1. Add "Card Background" as a color field, set to `#ffffff`
2. The CSS variable `--card-background` is now available
3. Use it in your CSS: `background: var(--card-background, #ffffff)`
***
## Best Practices
### Use Descriptive Names
* Card Background
* Header Text Color
* Button Hover Background
* Container Padding
* Primary Color
* Color 1
* Background
* Size
### Consistent Naming Patterns
```
[component]-[property]
card-background
card-border-color
card-shadow
card-radius
button-background
button-hover-background
button-text-color
button-radius
```
### Start Minimal
Don't create every possible field upfront. Let the AI add fields as needed, or add them when you actually need customization.
A simple app might have 5-10 design fields. A complex app might have 20-30.
### Dark Mode Pattern
Create paired light/dark fields:
```css theme={null}
:root {
--page-background: var(--light-page-background, #ffffff);
--text-color: var(--light-text-color, #1a1a1a);
}
:root.dark {
--page-background: var(--dark-page-background, #1a1a1a);
--text-color: var(--dark-text-color, #f5f5f5);
}
```
***
## Organizing Fields
Design fields appear in the order they're created. Group related fields by creating them together:
```
1. Page Background
2. Card Background
3. Header Background
(backgrounds grouped)
4. Heading Color
5. Body Text Color
6. Secondary Text Color
(text colors grouped)
7. Accent Color
8. Border Color
(UI colors grouped)
```
***
## FAQ
Yes! Click the pencil icon on any field to edit its name and type. The CSS variable will update automatically.
The CSS will fall back to the default value you specified: `var(--deleted-field, fallback)`. Always use fallbacks.
Use the "Custom" (text) field type and enter any font-family value. You'll need to ensure the font is loaded via a `` tag or `@font-face`.
Reference one variable from another:
```css theme={null}
--button-background: var(--accent-color, #3b82f6);
--link-color: var(--accent-color, #3b82f6);
```
Yes, read CSS variables with:
```javascript theme={null}
const color = getComputedStyle(document.documentElement)
.getPropertyValue('--accent-color')
```
# Docker
Source: https://docs.tinykit.studio/docker
Run tinykit in a Docker container
Deploy tinykit using Docker for consistent, reproducible deployments on any platform.
## Quick Start
```bash theme={null}
# Clone the repository
git clone https://github.com/tinykit-studio/tinykit.git
cd tinykit
# Build the image
docker build -f deploy/docker/Dockerfile -t tinykit .
# Run the container
docker run -d \
-p 3000:3000 \
-e LLM_PROVIDER=anthropic \
-e LLM_API_KEY=your-api-key \
-e POCKETBASE_ADMIN_EMAIL=admin@example.com \
-e POCKETBASE_ADMIN_PASSWORD=your-password \
-v tinykit-data:/app/pocketbase/pb_data \
--name tinykit \
tinykit
```
Access at `http://localhost:3000/tinykit`
***
## Dockerfile
tinykit includes a production-ready Dockerfile:
```dockerfile theme={null}
FROM node:20-alpine
WORKDIR /app
# Install dependencies
COPY package*.json ./
RUN npm ci --production=false
# Copy app
COPY . .
# Build
RUN npm run build
# Create directories for runtime
RUN mkdir -p pocketbase/pb_data workspace
# Expose port
EXPOSE 3000
ENV PORT=3000
ENV HOST=0.0.0.0
# Start
CMD ["./start.sh"]
```
***
## Environment Variables
Pass environment variables with `-e`:
```bash theme={null}
docker run -d \
-e LLM_PROVIDER=anthropic \
-e LLM_API_KEY=sk-ant-... \
-e LLM_MODEL=claude-sonnet-4-20250514 \
-e POCKETBASE_ADMIN_EMAIL=admin@example.com \
-e POCKETBASE_ADMIN_PASSWORD=securepassword123 \
tinykit
```
Or use an env file:
```bash theme={null}
# Create .env.production
cat > .env.production << EOF
LLM_PROVIDER=anthropic
LLM_API_KEY=sk-ant-...
LLM_MODEL=claude-sonnet-4-20250514
POCKETBASE_ADMIN_EMAIL=admin@example.com
POCKETBASE_ADMIN_PASSWORD=securepassword123
EOF
# Run with env file
docker run -d --env-file .env.production tinykit
```
***
## Data Persistence
Always use a volume for `/app/pocketbase/pb_data` to persist your database across container restarts.
### Named Volume (Recommended)
```bash theme={null}
docker run -d \
-v tinykit-data:/app/pocketbase/pb_data \
tinykit
```
### Bind Mount
```bash theme={null}
docker run -d \
-v /path/on/host/pb_data:/app/pocketbase/pb_data \
tinykit
```
***
## Docker Compose
For easier management, use Docker Compose:
```yaml theme={null}
# docker-compose.yml
version: '3.8'
services:
tinykit:
build:
context: .
dockerfile: deploy/docker/Dockerfile
ports:
- "3000:3000"
environment:
- LLM_PROVIDER=anthropic
- LLM_API_KEY=${LLM_API_KEY}
- LLM_MODEL=claude-sonnet-4-20250514
- POCKETBASE_ADMIN_EMAIL=admin@example.com
- POCKETBASE_ADMIN_PASSWORD=${POCKETBASE_ADMIN_PASSWORD}
volumes:
- tinykit-data:/app/pocketbase/pb_data
restart: unless-stopped
volumes:
tinykit-data:
```
Run with:
```bash theme={null}
# Set secrets in environment or .env file
export LLM_API_KEY=sk-ant-...
export POCKETBASE_ADMIN_PASSWORD=securepassword123
# Start
docker compose up -d
# View logs
docker compose logs -f
# Stop
docker compose down
```
***
## Production Deployment
### With Reverse Proxy (Caddy)
```yaml theme={null}
# docker-compose.yml
version: '3.8'
services:
tinykit:
build:
context: .
dockerfile: deploy/docker/Dockerfile
environment:
- LLM_PROVIDER=anthropic
- LLM_API_KEY=${LLM_API_KEY}
- POCKETBASE_ADMIN_EMAIL=admin@example.com
- POCKETBASE_ADMIN_PASSWORD=${POCKETBASE_ADMIN_PASSWORD}
volumes:
- tinykit-data:/app/pocketbase/pb_data
restart: unless-stopped
caddy:
image: caddy:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy-data:/data
restart: unless-stopped
volumes:
tinykit-data:
caddy-data:
```
```
# Caddyfile
app.yourdomain.com {
reverse_proxy tinykit:3000
}
```
### With nginx
```yaml theme={null}
# docker-compose.yml
services:
tinykit:
# ... same as above
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- /etc/letsencrypt:/etc/letsencrypt
restart: unless-stopped
```
***
## Health Checks
The container includes a health endpoint at `/tinykit`:
```yaml theme={null}
services:
tinykit:
# ...
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/tinykit"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
```
***
## Updating
```bash theme={null}
# Pull latest code
git pull
# Rebuild image
docker build -f deploy/docker/Dockerfile -t tinykit .
# Restart container
docker stop tinykit
docker rm tinykit
docker run -d \
-v tinykit-data:/app/pocketbase/pb_data \
--env-file .env.production \
-p 3000:3000 \
--name tinykit \
tinykit
```
Or with Docker Compose:
```bash theme={null}
git pull
docker compose build
docker compose up -d
```
***
## Troubleshooting
Check logs with `docker logs tinykit`. Common issues:
* Missing environment variables
* Invalid API key
* Port already in use
Ensure the Pocketbase binary is compatible with your architecture. The default binary is for linux/amd64. For ARM (M1/M2 Mac), you may need to rebuild.
Verify your volume mount:
```bash theme={null}
docker inspect tinykit | grep Mounts -A 20
```
Ensure `/app/pocketbase/pb_data` is mounted to a volume.
The container runs as root by default. If using bind mounts, ensure the host directory is writable.
***
## Resource Requirements
| Resource | Minimum | Recommended |
| -------- | ------- | ---------------------- |
| **CPU** | 1 core | 2 cores |
| **RAM** | 512MB | 1GB |
| **Disk** | 1GB | 5GB+ (depends on data) |
Limit resources with Docker:
```bash theme={null}
docker run -d \
--memory="1g" \
--cpus="1.0" \
tinykit
```
# Domain Routing
Source: https://docs.tinykit.studio/domain-routing
Run multiple apps from a single tinykit instance
tinykit supports **domain-based routing**—point multiple domains to one server, and each domain serves a different app.
```
recipes.yourserver.com → Recipe app
blog.yourserver.com → Blog app
calculator.yourserver.com → Calculator app
crm.yourserver.com → CRM app
```
All apps share the same builder dashboard. One deployment, unlimited apps.
***
## How It Works
When a request arrives, tinykit:
1. Extracts the domain from the request
2. Looks up the project with that domain in Pocketbase
3. Serves the production HTML for that project
```
Request: recipes.yourserver.com/
│
▼
Domain lookup: "recipes.yourserver.com"
│
▼
Find project with matching domain
│
▼
Serve project as static file (compiled HTML)
```
***
## URL Structure
For any domain pointing to your tinykit server:
| URL | What It Shows |
| ---------------------- | --------------------------------------- |
| `/` | Production app for this domain |
| `/tinykit` | Redirects to builder for this domain |
| `/tinykit/studio` | Builder for this domain's app |
| `/tinykit/dashboard` | List of ALL apps (same on every domain) |
| `/tinykit/studio?id=X` | Edit a specific app by ID |
The dashboard shows all apps regardless of which domain you access it from. Domains only affect which app is served at the root URL (`/`).
***
## Setting Up Multiple Domains
Deploy to Railway or your preferred host. Note your server's IP address or hostname.
Point your domains to your tinykit server:
```
recipes.yourserver.com A → 123.45.67.89
blog.yourserver.com A → 123.45.67.89
calculator.yourserver.com A → 123.45.67.89
```
Or use CNAME records if your host provides a hostname:
```
recipes.yourserver.com CNAME → your-app.railway.app
```
Visit each domain. If no app exists for that domain, you'll be redirected to create one:
```
Visit: recipes.yourserver.com
Redirect to: /tinykit/new?domain=recipes.yourserver.com
```
Build your app, and it's immediately live at that domain.
Railway and most platforms handle SSL automatically. For self-hosted setups, use a reverse proxy like Caddy or nginx with Let's Encrypt.
***
## Managing Apps
### From the Dashboard
Visit `/tinykit/dashboard` from any domain to see all your apps:
* Click an app to edit it
* See each app's domain
* Create new apps
### From Any Domain
Access the builder for the current domain:
```
recipes.yourserver.com/tinykit
```
Or jump directly to a specific app by ID:
```
recipes.yourserver.com/tinykit/studio?id=abc123
```
***
## Domain Normalization
tinykit normalizes domains for matching:
* Removes port numbers (`:5173`, `:3000`)
* Removes `www.` prefix
* Converts to lowercase
So these all resolve to the same project:
```
recipes.yourserver.com
www.recipes.yourserver.com
RECIPES.yourserver.com
recipes.yourserver.com:443
```
***
## Unknown Domains
When someone visits a domain that doesn't have a project:
```
newapp.yourserver.com/
│
▼
No project found for "newapp.yourserver.com"
│
▼
Redirect to: /tinykit/new?domain=newapp.yourserver.com
```
The new project page shows: "Creating app for newapp.yourserver.com"
***
## Example Setup
### Scenario: Agency with Multiple Clients
```
# Your clients' domains
client-a.com → Portfolio site
client-b.com → Booking system
client-c.com → Product catalog
# Your internal tools (subdomain)
admin.youragency.com → Internal dashboard
crm.youragency.com → Client CRM
```
All running on one \$5/month Railway instance.
### Scenario: Personal Projects
```
# Different subdomains of your domain
recipes.mydomain.com → Recipe collection
bookmarks.mydomain.com → Bookmark manager
notes.mydomain.com → Note-taking app
```
***
## Local Development
During local development, tinykit uses `localhost` as the domain. All apps are accessible via the dashboard at `/tinykit/dashboard`.
To test domain routing locally:
1. Add entries to your hosts file:
```
127.0.0.1 recipes.local
127.0.0.1 blog.local
```
2. Visit `recipes.local:5173` and `blog.local:5173`
3. Each will resolve to its respective project
***
## Deployment Considerations
### Railway
Railway provides a single URL (e.g., `your-app.railway.app`). To use custom domains:
1. Go to your Railway project settings
2. Add custom domains
3. Configure DNS as instructed
4. SSL is automatic
### Self-Hosted
For VPS deployments, use a reverse proxy:
Caddy handles SSL automatically:
```
recipes.yourserver.com {
reverse_proxy localhost:5173
}
blog.yourserver.com {
reverse_proxy localhost:5173
}
```
```nginx theme={null}
server {
listen 443 ssl;
server_name recipes.yourserver.com blog.yourserver.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:5173;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
```
***
## Limitations
Keep in mind:
* **Shared database**: All apps share the same Pocketbase instance. Collection names should be unique across apps, or use prefixes (e.g., `recipes_items`, `blog_posts`).
* **Shared authentication**: Pocketbase auth is shared. A user logged in on one domain is logged into all domains on that server.
* **Single server**: All apps run on the same server. High-traffic apps might need dedicated hosting.
***
## FAQ
Yes. Edit the project in the dashboard and change its domain field. The app will immediately be served at the new domain.
The first matching project is served. Avoid duplicate domains—each project should have a unique domain.
Yes. Apps without domains are only accessible via the dashboard using their ID (`/tinykit/studio?id=X`).
Yes. `app.example.com` and `other.example.com` are treated as different domains.
# FAQ
Source: https://docs.tinykit.studio/faq
Frequently asked questions about tinykit
## General
tinykit is an AI-powered development platform that lets you build, edit, and host web apps from a single deployment. Chat with AI to generate code, edit in a VS Code-like editor, and see changes live instantly.
The key difference: **your builder and your app run on the same server**.
With other tools, you build locally then deploy separately. With tinykit, there's no deploy step—your app is already live the moment you generate it.
Plus: open source, bring your own AI key, works on mobile, multi-app support, and costs \~\$10/month total.
tinykit is in **early beta**. Great for:
* Personal utilities and side projects
* Internal tools and admin dashboards
* MVPs and prototypes
**Keep in mind:**
* No guarantees against data loss—back up anything important
* Auth requires manual wiring (no built-in user auth for generated apps)
* Best suited for internal use or tools where you control access
Not recommended (yet) for public-facing apps with sensitive user data.
Yes! MIT licensed. Fork it, customize it, self-host it, sell services with it. [View on GitHub](https://github.com/tinykit-studio/tinykit)
## Deployment & Hosting
No. Railway is the easiest option (one-click deploy), but you can host anywhere that runs Node.js:
* Fly.io
* DigitalOcean
* AWS
* Any VPS
* Docker
See [Architecture](/architecture#deployment-options) for deployment guides.
Yes! tinykit supports **domain-based routing**. Point multiple domains to your server and each serves a different app:
```
recipes.yourserver.com → Recipe app
blog.yourserver.com → Blog app
crm.yourserver.com → CRM app
```
Run hundreds of apps from a single tinykit instance.
**For static apps (no database):** Yes, export the built HTML and deploy anywhere.
**For apps using the database:** No - these require tinykit's backend APIs (`/_tk/data`, `/_tk/realtime`). Keep them hosted on your tinykit instance, or use domain-based routing to serve multiple apps from one server.
Yes. Configure your custom domain in Railway (or your hosting platform) and point your DNS there. tinykit automatically serves the right app based on the domain.
Railway and most modern platforms provide automatic HTTPS. No configuration needed.
## AI & API Keys
* **OpenAI** - GPT-4o, GPT-4 Turbo, GPT-3.5
* **Anthropic** - Claude Sonnet 4, Claude 3.5 Sonnet
* **Google Gemini** - Gemini Pro, Gemini Flash
This keeps costs transparent and in your control. You pay your AI provider directly for what you use—no markup, no hidden fees, no subscription.
Typical casual usage: \$3-5/month with GPT-4 or Claude.
Heavy usage (building apps daily): \$10-20/month.
You can monitor and set limits in your provider's dashboard.
Yes! If you already pay for Claude Pro, ChatGPT Plus, or another LLM subscription, you can generate apps in those interfaces and import them into tinykit.
See [No API Key Required](/no-api-key) for the snapshot format and prompt templates.
## Builder Interface
| Tab | Purpose | Shortcut |
| ----------- | -------------------------------------- | -------- |
| **Agent** | Chat with AI to build your app | `Cmd+1` |
| **Code** | Edit code directly | `Cmd+2` |
| **Content** | CMS fields for non-developers | `Cmd+3` |
| **Design** | CSS variables (colors, fonts, spacing) | `Cmd+4` |
| **Data** | Browse and edit database records | `Cmd+5` |
| **History** | Snapshots for undo/time travel | `Cmd+6` |
Design fields are CSS variables that control your app's appearance. The AI creates these as it builds, and non-technical users can customize them without code:
* Colors (primary, secondary, background)
* Fonts (family, sizes)
* Spacing (padding, margins)
* Borders (radius, shadows)
Content fields are CMS-like editable values—headlines, descriptions, button text. Perfect for letting clients update copy without accessing the code editor.
Yes! The editor and AI work on tablets. Perfect for quick edits on the go.
## Features & Usage
tinykit generates **Svelte 5** apps using the runes API (`$state`, `$derived`, `$effect`). Apps compile to standalone HTML with CSS-in-style blocks.
The AI is specifically tuned for this stack with detailed system prompts.
Yes! Snapshots are built in. Every AI change creates a snapshot. Click any snapshot in the History tab to restore your code, design fields, and content to that point. Database records are not affected.
tinykit includes **PocketBase**—an embedded SQLite database with:
* Built-in authentication
* Real-time subscriptions
* Admin UI at `/_pb/_`
* REST API
The AI creates collections automatically when you ask for data storage.
14 starter templates across categories:
| Category | Templates |
| ---------------- | ---------------------------------- |
| **Productivity** | Kanban, Notes, Canvas, Timer |
| **Finance** | Expense tracker, Invoice generator |
| **Content** | Bookmarks, Recipes |
| **Social** | Linktree, Poll, Event RSVP |
| **Business** | Client CRM |
| **News** | HN reader, RSS reader |
## Troubleshooting
Check:
1. Your API key is correct in `.env`
2. You have credits/quota with your AI provider
3. The model name is correct (e.g., `gpt-4o`, not `gpt4`)
Check your provider's dashboard for usage and errors.
Try:
1. Hard refresh the preview (Cmd+Shift+R)
2. Check the browser console for errors
3. Ensure files are saving (check for save indicator)
Check:
1. File size is under 5MB
2. File extension isn't blocked (no .exe, .sh, etc.)
3. Path doesn't contain `../`
Check browser console for specific error messages.
Check:
1. Your domain's DNS is pointing to your tinykit server
2. The domain is associated with a project in the dashboard
3. SSL/HTTPS is properly configured
Visit `/tinykit/dashboard` to see all projects and their domains.
## Still Have Questions?
Ask the community
Found a bug? Let us know
# Features
Source: https://docs.tinykit.studio/features
Overview of tinykit builder capabilities
The builder interface provides tools for code generation, manual editing, and configuration.
## AI Agent
The AI Agent generates and modifies code based on natural language prompts. It accesses project context, including existing code and configuration fields, to make informed changes.
**Capabilities:**
* **Component Generation**: Creates Svelte app from descriptions.
* **Code Modification**: Updates existing files and logic.
* **Debugging**: Analyzes and fixes errors.
* **Field generation/integration**: Creates and integrates content and design fields with the app code.
* **Data Integration**: Sets up data collections and subscriptions.
## Code Editor
A CodeMirror 6-based editor allows for direct code manipulation.
**Features:**
* Syntax highlighting (Svelte, JS, CSS, HTML).
* Live auto-save and preview sizing.
* Standard keyboard shortcuts (Cmd+S to save, Cmd+F to find).
## Live Preview
The preview pane displays the running application in a sandboxed iframe. Changes are reflected immediately upon save or generation.
## Content Fields
Content fields decouple text content from code, allowing updates without code deployment.
* **Usage**: Fields are referenced in code (e.g., `content.title`).
* **Types**: Text, Textarea, Number, Boolean, Image, Markdown.
## Design System
Design tokens are managed via CSS variables, accessible through the Design panel.
* **Usage**: Variables are used in CSS (e.g., `var(--color-primary)`).
* **Types**: Color, Font, Size, Radius, Shadow.
## Database
A built-in PocketBase instance manages data persistence.
* **Integration**: Local PocketBase instance running alongside the app.
* **Access**: Admin UI available at `/_pb/_`.
* **Realtime**: App data subscriptions set up automatically by the agent.
## Snapshots
The system automatically creates snapshots for every AI generation iteration. Users can also manually create snapshots to version the state.
## Multi-App Routing
tinykit supports serving multiple applications from a single instance using domain-based routing.
**How it works:**
```
recipes.yourserver.com/ → Serves recipe app
recipes.yourserver.com/tinykit → Edit recipe app
blog.yourserver.com/ → Serves blog app
blog.yourserver.com/tinykit → Edit blog app
calculator.yourserver.com/ → Serves calculator app
...
```
Point any domain to your tinykit server, and it automatically serves the right app.
***
## Starter Templates
14 templates included to jumpstart your projects:
| Category | Templates |
| ---------------- | ---------------------------------- |
| **Productivity** | Kanban, Notes, Canvas, Timer |
| **Finance** | Expense tracker, Invoice generator |
| **Content** | Bookmarks, Recipes |
| **Social** | Linktree, Poll, Event RSVP |
| **Business** | Client CRM |
| **News** | HN reader, RSS reader |
Or start from scratch and let AI build exactly what you need.
# Tinykit
Source: https://docs.tinykit.studio/index
Self-hosted agentic app builder for your infrastructure.
Tinykit is an open-source tool for building and deploying tiny web applications. It combines an AI agent, an editor, and a runtime into a single instance, allowing you to host 100+ apps on one server and instantly update them by typing `/Tinykit` after their domain name.
## Overview
Tinykit is in **early alpha**. It's best suited for personal utility tools, small business CRUD apps, and internal tools. AI-generated code should be reviewed before handling sensitive data or running in production environments.
Unlike cloud-based builders that separate development from hosting, Tinykit runs the builder and built applications on the same server. The builder (or studio) is always available and can deploy app updates in seconds.
## Workflow
1. **Prompt**: Describe the application or feature to the AI agent.
2. **Generate**: The agent writes code, creates and integrates fields and datbase tables.
3. **Refine**: Edit code manually, update content and design from fields, or continue prompting.
4. **Deploy**: Push the 'Deploy' button, wait 1-2 seconds, enjoy your app.
5. **Repeat**: Just to go `yourappsdomain.com/tinykit` to keep working on it, the studio's always there.
## Comparison
| Feature | Tinykit | Builder Services | Traditional IDEs |
| :--------------- | :------------------------ | :-------------------- | :--------------- |
| **Hosting** | Self-hosted (VPS/Railway) | Managed Cloud | External |
| **Runtime** | Single Server | Separate Build/Deploy | Local/CI |
| **Backend** | Built-in PocketBase | Internal/External | External |
| **Pricing** | Infrastructure Cost | Subscription | License/Free |
| **Data Control** | User Owned | Provider Managed | User Owned |
## Core Components
AI-driven code generation and manual editing tools.
Technical overview of the single-server model.
Built-in PocketBase integration and schemas.
## What Can You Build?
Admin dashboards, data entry forms, reporting tools
Landing pages, booking systems, contact forms
CRM systems, inventory trackers, project managers
Analytics dashboards, calculators, converters
Personal sites, galleries, blogs
Validate ideas fast before committing
## Perfect For
Ship client work in hours, not days. Generate CRUD apps on the fly, customize with AI, and hand off a production URL.
Build internal tools without pulling developers off client work. Spin up admin dashboards, reporting tools, and data entry forms.
Validate your SaaS idea this weekend. Build an MVP, get it live, and start collecting feedback immediately.
Skip the boilerplate. Let AI handle the scaffolding while you focus on business logic.
# Quick Start
Source: https://docs.tinykit.studio/quickstart
Deployment and setup instructions.
tinykit can be deployed to Railway or run locally.
## Railway Deployment
Deploy a new instance in less than a minute with a single click, no configuration necessary. This template provisions the necessary environment variables and a persistent volume.
[](https://railway.com/deploy/tinykit?referralCode=RCPU7k\&utm_medium=integration\&utm_source=template\&utm_campaign=generic)
### Configuration
During deployment, configure the LLM provider.
| Variable | Description | Example |
| :------------- | :--------------------------------- | :---------- |
| `LLM_PROVIDER` | `openai`, `anthropic`, or `gemini` | `anthropic` |
| `LLM_API_KEY` | Provider API key | `sk-...` |
| `LLM_MODEL` | Provider model ID | `gpt-4o` |
Once deployed, access the builder at `/tinykit` (e.g., `https://your-project.up.railway.app/tinykit`).
## Local Development
### Prerequisites
* Node.js 20+
* Git
### Setup
1. **Clone**:
```bash theme={null}
git clone https://github.com/tinykit-studio/tinykit.git
cd tinykit
```
2. **Install**:
```bash theme={null}
npm install
```
3. **Configure**:
cp `.env.example` to `.env` and set the `LLM_*` variables.
4. **Database**:
Download the PocketBase binary.
```bash theme={null}
npm run pocketbase:download
```
5. **Run**:
Start the development server.
```bash theme={null}
npm run dev
```
Access at `http://localhost:5173/tinykit`.
## Environment Variables
| Variable | Required | Description |
| :------------- | :------- | :------------------------------------- |
| `LLM_PROVIDER` | No\* | AI provider slug. |
| `LLM_API_KEY` | No\* | Provider API Key. |
| `LLM_MODEL` | No | Model ID. |
| `LLM_BASE_URL` | No | Optional base URL for compatible APIs. |
\*Can also be configured via `/tinykit/settings` UI after deployment.
## Estimated Costs
**Railway (Hobby)**: \~\$5/month (variable based on usage).
**LLM API**: Billed directly by the provider (OpenAI, Anthropic, etc).
# Railway
Source: https://docs.tinykit.studio/railway
Deploy tinykit to Railway with one click
[Railway](https://railway.app) is the easiest way to deploy tinykit. One click, automatic HTTPS, and persistent storage included.
## One-Click Deploy
Click to deploy tinykit to Railway in under 5 minutes.
***
## What You'll Need
1. A [Railway account](https://railway.app) (free tier available)
2. An AI API key from one of:
* [Anthropic](https://console.anthropic.com/) (Claude)
* [OpenAI](https://platform.openai.com/api-keys) (GPT-4)
* [Google AI Studio](https://aistudio.google.com/apikey) (Gemini)
***
## Step-by-Step Setup
Click the "Deploy to Railway" button above, or go to the [tinykit Railway template](https://railway.com/deploy/tinykit?referralCode=RCPU7k\&utm_medium=integration\&utm_source=template\&utm_campaign=generic).
Railway will prompt you to set environment variables:
| Variable | Required | Description |
| --------------------------- | -------- | ---------------------------------------- |
| `LLM_PROVIDER` | Yes | `anthropic`, `openai`, or `gemini` |
| `LLM_API_KEY` | Yes | Your API key |
| `LLM_MODEL` | No | Model name (defaults to provider's best) |
| `POCKETBASE_ADMIN_EMAIL` | Yes | Admin email for database |
| `POCKETBASE_ADMIN_PASSWORD` | Yes | Admin password (min 8 chars) |
Example configuration:
```
LLM_PROVIDER=anthropic
LLM_API_KEY=sk-ant-api03-...
LLM_MODEL=claude-sonnet-4-20250514
POCKETBASE_ADMIN_EMAIL=admin@yourdomain.com
POCKETBASE_ADMIN_PASSWORD=your-secure-password
```
Click "Deploy" and wait 2-3 minutes for the build to complete.
Once deployed, Railway provides a URL like `your-app.railway.app`.
* **Builder**: `your-app.railway.app/tinykit`
* **Production apps**: `your-app.railway.app/`
***
## Adding Custom Domains
Go to your Railway project → Settings → Domains
Click "Add Domain" and enter your domain (e.g., `app.yourdomain.com`)
Add a CNAME record pointing to your Railway URL:
```
app.yourdomain.com CNAME your-app.railway.app
```
Railway automatically provisions SSL certificates. This takes 1-5 minutes.
You can add multiple domains to serve different apps. Each domain can point to a different tinykit project via [domain routing](/domain-routing).
***
## Persistent Storage
Railway automatically persists the `pocketbase/pb_data` directory. Your data survives:
* Redeploys
* Restarts
* Sleep/wake cycles
Railway's free tier has storage limits. For production apps with significant data, consider upgrading to a paid plan.
***
## Costs
| Plan | Monthly Cost | Includes |
| --------- | ------------ | ------------------------------- |
| **Hobby** | \~\$5 | 500 hours, 1GB storage |
| **Pro** | \$20 base | Unlimited hours, more resources |
Plus your AI API costs (typically \$3-10/month for casual use).
***
## Updating Your Deployment
### Automatic Updates
Fork the tinykit repository and connect it to Railway for automatic deployments on push.
### Manual Updates
1. Go to your Railway project
2. Click "Redeploy" to pull the latest changes
***
## Environment Variables Reference
| Variable | Default | Description |
| --------------------------- | --------- | ----------------------------------------------------- |
| `LLM_PROVIDER` | - | AI provider: `anthropic`, `openai`, `gemini` |
| `LLM_API_KEY` | - | Your AI API key |
| `LLM_MODEL` | varies | Model to use (e.g., `claude-sonnet-4-20250514`) |
| `LLM_BASE_URL` | - | Custom API endpoint (for OpenAI-compatible providers) |
| `POCKETBASE_ADMIN_EMAIL` | - | Pocketbase admin email |
| `POCKETBASE_ADMIN_PASSWORD` | - | Pocketbase admin password |
| `PORT` | `3000` | Server port (Railway sets this automatically) |
| `HOST` | `0.0.0.0` | Server host |
***
## Troubleshooting
Check that your `POCKETBASE_ADMIN_EMAIL` and `POCKETBASE_ADMIN_PASSWORD` are set. The password must be at least 8 characters.
Verify your `LLM_API_KEY` is correct and has credits. Check the Railway logs for specific error messages.
Wait 5-10 minutes for SSL provisioning. If it persists, verify your DNS records are correct.
Railway should persist `/pocketbase/pb_data`. If data is lost, check that you haven't accidentally removed the volume in Railway settings.
Railway's free tier sleeps after inactivity. The first request after sleep takes 10-20 seconds. Upgrade to a paid plan for always-on performance.
***
## Logs and Monitoring
Access logs from the Railway dashboard:
1. Go to your project
2. Click on the service
3. View "Deployments" for build logs
4. View "Logs" for runtime logs
Look for:
* `Starting Pocketbase...` → Database startup
* `Pocketbase is ready!` → Database ready
* `Starting SvelteKit server...` → App startup
# Security
Source: https://docs.tinykit.studio/security
Security features and best practices
tinykit is designed for **self-hosted deployments** where you control the server. This gives you full control over security, but also means you're responsible for it.
## Security Model
Since tinykit is self-hosted (one server = one team), there's no multi-tenant isolation. All authenticated users can access all projects on that instance.
### What's Protected
| Layer | Protection |
| ------------------- | --------------------------------------------------- |
| **Builder access** | Pocketbase authentication required for `/tinykit` |
| **API keys** | Stored server-side in `.env`, never sent to browser |
| **File operations** | Scoped to workspace directory |
| **Preview** | Sandboxed iframe with restricted permissions |
| **Database** | Pocketbase with collection-level access rules |
***
## Built-in Protections
tinykit includes several security measures out of the box:
Pocketbase auth with JWT tokens and automatic refresh
All file paths validated, `../` attacks blocked
Cross-origin requests blocked for data APIs
Preview runs in isolated iframe with `allow-scripts allow-same-origin`
API keys and credentials never exposed to client
Database accessed via same-origin proxy at `/_pb/`
***
## Production Checklist
Before exposing your tinykit instance to the public:
The `/tinykit` path gives full access to your codebase. Add authentication before going public.
**Critical:** Never expose `/tinykit` without authentication in production.
Never hardcode API keys or secrets. Use `.env` for configuration.
```env theme={null}
LLM_API_KEY=sk-...
```
Railway and most platforms provide HTTPS automatically. Never run without it.
Watch for unusual traffic patterns or error spikes.
***
## Scheduling PocketBase Backups
Your PocketBase database (`pb_data`) contains all your data. Regular backups are essential.
### Manual Backups
Access the PocketBase admin at `/_pb/_` and use the built-in backup feature under **Settings > Backups**.
### Automated Backups
Create a backup script and schedule it with cron:
```bash theme={null}
# backup.sh
#!/bin/bash
BACKUP_DIR="/path/to/backups"
CONTAINER="tinykit"
DATE=$(date +%Y%m%d_%H%M%S)
# Stop writes temporarily (optional, for consistency)
docker exec $CONTAINER /app/pocketbase/pocketbase backup
# Copy the backup
docker cp $CONTAINER:/app/pocketbase/pb_data/backups/. $BACKUP_DIR/
# Keep only last 7 days
find $BACKUP_DIR -name "*.zip" -mtime +7 -delete
```
Schedule with cron:
```bash theme={null}
# Run daily at 2am
0 2 * * * /path/to/backup.sh
```
If using a named volume, back it up directly:
```bash theme={null}
# Create backup
docker run --rm \
-v tinykit-data:/data \
-v $(pwd):/backup \
alpine tar czf /backup/pb_data_$(date +%Y%m%d).tar.gz -C /data .
# Restore from backup
docker run --rm \
-v tinykit-data:/data \
-v $(pwd):/backup \
alpine sh -c "cd /data && tar xzf /backup/pb_data_20231215.tar.gz"
```
Test your restore process before you need it. A backup you can't restore is worthless.
***
## Adding Authentication
To protect the `/tinykit` route, you have several options:
tinykit uses PocketBase for authentication. Create users in the PocketBase admin (`/_pb/_`) and they can log in to access the builder.
* Email/password authentication
* JWT tokens with automatic refresh
* Per-user accounts
Add authentication at the proxy level (nginx, Cloudflare Access, etc.):
```nginx theme={null}
location /tinykit {
auth_basic "Admin Area";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://localhost:5173;
}
```
Only allow access from specific IP addresses (good for internal tools).
```nginx theme={null}
location /tinykit {
allow 203.0.113.0/24;
deny all;
proxy_pass http://localhost:5173;
}
```
***
## Reporting Vulnerabilities
Found a security issue? Please report it responsibly:
Open a private security advisory on GitHub
We take security seriously and will respond promptly to legitimate reports.
# VPS / Self-Hosted
Source: https://docs.tinykit.studio/vps
Deploy tinykit on your own server
Run tinykit on any Linux VPS—DigitalOcean, Linode, Hetzner, AWS, or your own hardware.
## Requirements
* **OS**: Ubuntu 22.04+ / Debian 12+ (or any Linux with Node.js support)
* **RAM**: 1GB minimum, 2GB recommended
* **Disk**: 5GB minimum
* **Node.js**: 20.x or later
* **Ports**: 80, 443 (with reverse proxy) or custom port
***
## Quick Setup
```bash theme={null}
# Ubuntu/Debian
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
# Verify
node --version # Should be v20.x.x
npm --version
```
```bash theme={null}
# Clone repository
git clone https://github.com/tinykit-studio/tinykit.git
cd tinykit
# Install dependencies
npm install
# Build for production
npm run build
```
```bash theme={null}
# Copy example config
cp .env.example .env
# Edit with your settings
nano .env
```
Set these variables:
```bash theme={null}
LLM_PROVIDER=anthropic
LLM_API_KEY=sk-ant-...
LLM_MODEL=claude-sonnet-4-20250514
PORT=3000
HOST=0.0.0.0
```
```bash theme={null}
npm run pocketbase:download
```
```bash theme={null}
# Make start script executable
chmod +x deploy/railway/start.sh
# Start (for testing)
./deploy/railway/start.sh
```
***
## Production Setup with PM2
Use PM2 to keep tinykit running and restart on crashes:
```bash theme={null}
# Install PM2 globally
npm install -g pm2
# Create PM2 ecosystem file
cat > ecosystem.config.js << 'EOF'
module.exports = {
apps: [{
name: 'tinykit',
script: './deploy/railway/start.sh',
cwd: '/path/to/tinykit',
env: {
NODE_ENV: 'production',
PORT: 3000,
HOST: '0.0.0.0'
},
// Restart on failure
restart_delay: 5000,
max_restarts: 10,
// Logging
error_file: './logs/error.log',
out_file: './logs/out.log',
merge_logs: true,
time: true
}]
}
EOF
# Start with PM2
pm2 start ecosystem.config.js
# Save PM2 config (survives reboot)
pm2 save
# Enable PM2 startup on boot
pm2 startup
```
### PM2 Commands
```bash theme={null}
pm2 status # View status
pm2 logs tinykit # View logs
pm2 restart tinykit # Restart
pm2 stop tinykit # Stop
pm2 delete tinykit # Remove from PM2
```
***
## Reverse Proxy Setup
### Caddy (Recommended)
Caddy automatically handles SSL certificates:
```bash theme={null}
# Install Caddy
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy
```
Create Caddyfile:
```bash theme={null}
sudo nano /etc/caddy/Caddyfile
```
```
app.yourdomain.com {
reverse_proxy localhost:3000
}
# Multiple domains for different apps
recipes.yourdomain.com {
reverse_proxy localhost:3000
}
blog.yourdomain.com {
reverse_proxy localhost:3000
}
```
```bash theme={null}
# Reload Caddy
sudo systemctl reload caddy
```
### nginx
```bash theme={null}
# Install nginx
sudo apt install nginx
# Create config
sudo nano /etc/nginx/sites-available/tinykit
```
```nginx theme={null}
server {
listen 80;
server_name app.yourdomain.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
```
```bash theme={null}
# Enable site
sudo ln -s /etc/nginx/sites-available/tinykit /etc/nginx/sites-enabled/
# Test config
sudo nginx -t
# Reload nginx
sudo systemctl reload nginx
# Add SSL with Certbot
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d app.yourdomain.com
```
***
## Systemd Service (Alternative to PM2)
Create a systemd service for automatic startup:
```bash theme={null}
sudo nano /etc/systemd/system/tinykit.service
```
```ini theme={null}
[Unit]
Description=tinykit
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/path/to/tinykit
ExecStart=/path/to/tinykit/deploy/railway/start.sh
Restart=on-failure
RestartSec=10
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=tinykit
Environment=NODE_ENV=production
Environment=PORT=3000
Environment=HOST=0.0.0.0
Environment=LLM_PROVIDER=anthropic
Environment=LLM_API_KEY=sk-ant-...
[Install]
WantedBy=multi-user.target
```
```bash theme={null}
# Reload systemd
sudo systemctl daemon-reload
# Enable on boot
sudo systemctl enable tinykit
# Start
sudo systemctl start tinykit
# Check status
sudo systemctl status tinykit
# View logs
sudo journalctl -u tinykit -f
```
***
## Firewall Setup
```bash theme={null}
# Allow HTTP/HTTPS
sudo ufw allow 80
sudo ufw allow 443
# Or allow custom port
sudo ufw allow 3000
# Enable firewall
sudo ufw enable
```
***
## Updating
```bash theme={null}
cd /path/to/tinykit
# Pull latest
git pull
# Install new dependencies
npm install
# Rebuild
npm run build
# Restart
pm2 restart tinykit
# or
sudo systemctl restart tinykit
```
***
## Backup
### Database Backup
```bash theme={null}
# Stop the app (optional, for consistent backup)
pm2 stop tinykit
# Copy database
cp -r pocketbase/pb_data /backup/tinykit-$(date +%Y%m%d)
# Restart
pm2 start tinykit
```
### Automated Backups
```bash theme={null}
# Add to crontab
crontab -e
```
```
# Daily backup at 2 AM
0 2 * * * cp -r /path/to/tinykit/pocketbase/pb_data /backup/tinykit-$(date +\%Y\%m\%d)
# Keep only last 7 days
0 3 * * * find /backup -name "tinykit-*" -mtime +7 -delete
```
***
## Troubleshooting
Check permissions:
```bash theme={null}
chmod +x pocketbase/pocketbase
ls -la pocketbase/
```
Ensure the binary matches your architecture (amd64 vs arm64).
Ensure the user running tinykit owns the directory:
```bash theme={null}
sudo chown -R $USER:$USER /path/to/tinykit
```
Check what's using the port:
```bash theme={null}
sudo lsof -i :3000
```
Kill the process or change the PORT in your config.
With Caddy, certificates are automatic. With nginx + Certbot:
```bash theme={null}
sudo certbot renew --dry-run
```
Check memory usage:
```bash theme={null}
free -h
htop
```
Consider adding swap or upgrading your VPS.
***
## Security Checklist
* [ ] Keep the system updated: `sudo apt update && sudo apt upgrade`
* [ ] Use strong passwords for Pocketbase admin
* [ ] Enable firewall (ufw)
* [ ] Use HTTPS (Caddy or Certbot)
* [ ] Don't expose port 3000 directly—use a reverse proxy
* [ ] Regular backups
* [ ] Consider fail2ban for brute-force protection