Self-Hosting Guide
Deploy your own Daslab instance with a custom domain, OAuth integrations, and full control over your data.
Architecture Overview
Daslab has a simple architecture:
| Component | Technology | Purpose |
|---|---|---|
| Server | Bun + Hono | API, website, OAuth, WebSockets |
| Database | PostgreSQL | Users, orgs, jobs, credentials |
| Redis | Upstash Redis | Job queues, caching, rate limiting |
| File Storage | Cloudflare R2 (S3-compatible) | User uploads, CLI releases |
| Push Notifications | APNs | iOS push notifications |
| Resend | Transactional email (magic links, invites) |
The server is a single Bun process that handles everything — API requests, website pages, OAuth flows, WebSocket connections, and background job processing.
Prerequisites
- A domain name (e.g.
daslab.run) - A server or PaaS that can run Bun (Render, Railway, Fly.io, VPS, etc.)
- PostgreSQL database
- Redis instance (Upstash recommended)
- Cloudflare account (for R2 file storage)
1. Domain & DNS Setup
You'll need these DNS records pointing to your infrastructure:
| Record | Type | Target | Purpose |
|---|---|---|---|
yourdomain.com | CNAME | Your server host | API + website |
tmp.yourdomain.com | CNAME | R2 custom domain | User file uploads |
releases.yourdomain.com | CNAME | R2 custom domain | CLI binary releases |
Cloudflare R2 Custom Domains
For file storage, set up two R2 buckets with custom domains:
- Go to Cloudflare Dashboard > R2 > Create Bucket
- Create a bucket for user uploads (e.g.
daslab-user) - Under Settings > Custom Domains, add
tmp.yourdomain.com - Repeat for CLI releases bucket with
releases.yourdomain.com
Cloudflare automatically manages the DNS and SSL for R2 custom domains.
2. Environment Variables
Set these on your server. The only required variable for domain configuration is PUBLIC_BASE_URL — all OAuth callback URLs, canonical URLs, sitemaps, and SEO metadata derive from it automatically.
Core Configuration
# Your domain — this is the single source of truth for all server URLs
PUBLIC_BASE_URL=https://yourdomain.com
# Database
DATABASE_URL=postgresql://user:pass@host:5432/daslab
# Redis
UPSTASH_REDIS_URL=rediss://default:xxx@xxx.upstash.io:6379
# File storage (Cloudflare R2)
R2_ACCESS_KEY_ID=your_r2_access_key
R2_SECRET_ACCESS_KEY=your_r2_secret_key
R2_ACCOUNT_ID=your_cloudflare_account_id
R2_BUCKET_NAME=daslab-releases
R2_USER_BUCKET_NAME=daslab-user
R2_PUBLIC_URL=https://tmp.yourdomain.com # Public URL for user file uploads
# Email
FROM_EMAIL=Daslab <noreply@yourdomain.com> # Sender address for transactional email
CONTACT_EMAIL=hello@yourdomain.com # Contact email shown in legal pages
AI Providers
At least one LLM provider key is required. Add as many as you need:
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...
OPENROUTER_API_KEY=sk-or-...
GEMINI_API_KEY=...
Email (Resend)
Required for magic link authentication and email invites:
RESEND_API_KEY=re_...
Emails are sent from the address configured in FROM_EMAIL. Configure your domain in the Resend dashboard to verify DNS records (SPF, DKIM, DMARC).
Apple Push Notifications
Required for iOS push notifications:
APNS_TEAM_ID=your_apple_team_id
APNS_KEY_ID=your_apns_key_id
APNS_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
APNS_BUNDLE_ID=com.yourcompany.Daslab
Observability (Optional)
OTEL_EXPORTER_OTLP_ENDPOINT=https://your-otel-collector:4318 # OpenTelemetry traces
ADMIN_LOGS_KEY=your_admin_logs_key # Key for /admin/logs endpoint
3. OAuth Provider Setup
Daslab supports authentication and integration via several OAuth providers. Each requires registering an application on the provider's developer portal and configuring the callback URL.
All callback URLs follow the pattern: https://yourdomain.com/auth/{provider}/callback
GitHub
Used for: User login/signup, GitHub repository access- Go to GitHub Developer Settings
- Click New OAuth App (or edit existing)
- Set the fields:
- Homepage URL: https://yourdomain.com
- Authorization callback URL: https://yourdomain.com/auth/github/callback
- Copy the Client ID and generate a Client Secret
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
Scopes requested at runtime: user:email, repo, read:org
- Go to Google Cloud Console > APIs & Credentials
- Click Create Credentials > OAuth 2.0 Client ID
- Set application type to Web application
- Add these Authorized redirect URIs (both are required):
https://yourdomain.com/auth/google/callback (for service integrations)
- https://yourdomain.com/auth/google/signin/callback (for user login)
- Copy the Client ID and Client Secret
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
Scopes requested at runtime:
- Sign-in:
openid,email,profile - Gmail:
gmail.readonly,gmail.send - Calendar:
calendar,calendar.events - Drive:
drive - Sheets:
spreadsheets,drive.readonly
Note: Google requires an OAuth consent screen to be configured. For internal/testing use, set the app to "Internal" (Google Workspace) or "Testing" (personal accounts, limited to 100 test users). For production, submit for verification.
Apple
Used for: User login/signup (Sign in with Apple)Apple Sign-In uses native iOS authentication — there is no server-side OAuth redirect. The iOS app handles authentication directly via ASAuthorization and sends the identity token to the server for verification.
No callback URL configuration is needed. The server verifies tokens against Apple's JWKS endpoint (https://appleid.apple.com/auth/keys).
# Defaults to your app's bundle identifier
APPLE_CLIENT_ID=com.yourcompany.Daslab
Figma
Used for: Figma file access (organization-level integration)- Go to the Figma Developer Portal
- Create a new app or edit an existing one
- Set the Callback URL:
https://yourdomain.com/auth/figma/callback - Copy the Client ID and Client Secret
FIGMA_CLIENT_ID=your_figma_client_id
FIGMA_CLIENT_SECRET=your_figma_client_secret
Scopes requested at runtime: files:read
Slack
Used for: Slack workspace access (organization-level integration)- Go to Slack API > Your Apps
- Create a new app — you can use the manifest at
server/slack-manifest.yml(update the URLs in the manifest to your domain first) - Under OAuth & Permissions, add a Redirect URL:
https://yourdomain.com/auth/slack/callback
- Under Event Subscriptions, set the Request URL:
https://yourdomain.com/webhooks/slack/events
- Copy the Client ID and Client Secret from Basic Information
SLACK_CLIENT_ID=your_slack_client_id
SLACK_CLIENT_SECRET=your_slack_client_secret
SLACK_SIGNING_SECRET=your_slack_signing_secret # For webhook signature verification
Bot scopes: app_mentions:read, channels:history, channels:read, chat:write, chat:write.customize, commands, groups:history, groups:read, im:history, im:read, reactions:read, reactions:write, users:read
User scopes: channels:history, channels:read, groups:history, groups:read, im:history, im:read, search:read
Note: The server/slack-manifest.yml file contains hardcoded URLs that must be updated to your domain before creating the Slack app from the manifest.
Canva
Used for: Canva design access, assets, comments (organization-level integration)- Go to the Canva Developer Portal
- Create a new integration
- Set the Redirect URL:
https://yourdomain.com/auth/canva/callback - Set the Webhook URL:
https://yourdomain.com/webhooks/canva/default - Copy the Client ID and Client Secret
CANVA_CLIENT_ID=your_canva_client_id
CANVA_CLIENT_SECRET=your_canva_client_secret
Scopes requested at runtime: design:meta:read, design:content:read, design:content:write, asset:read, asset:write, brandtemplate:meta:read, brandtemplate:content:read, folder:read, folder:write, comment:read, comment:write, profile:read, collaboration:event
Note: Canva uses PKCE (S256) for the OAuth flow. The server handles code verifier/challenge generation automatically.
Xero
Used for: Accounting access — invoices, payments, bank transactions, contacts (organization-level integration)Xero is multi-tenant: one OAuth connection can access multiple organizations. Daslab creates one asset per tenant.
- Go to the Xero Developer Portal
- Create a new app (Web App type)
- Set the Redirect URI:
https://yourdomain.com/auth/xero/callback - Copy the Client ID and Client Secret
XERO_CLIENT_ID=your_xero_client_id
XERO_CLIENT_SECRET=your_xero_client_secret
Scopes requested at runtime: openid, profile, email, offline_access, accounting.invoices, accounting.payments.read, accounting.banktransactions.read, accounting.contacts.read, accounting.attachments.read, accounting.settings.read
Upwork
Used for: Upwork organization/freelancer access (organization-level integration)- Go to the Upwork Developer Portal
- Create a new API application
- Set the Callback URL:
https://yourdomain.com/auth/upwork/callback - Copy the Client ID and Client Secret
UPWORK_CLIENT_ID=your_upwork_client_id
UPWORK_CLIENT_SECRET=your_upwork_client_secret
LINE
Used for: LINE messaging bot with account linking (webhook-based, not OAuth)- Go to the LINE Developers Console
- Create a Messaging API channel
- Under Messaging API, set the Webhook URL:
https://yourdomain.com/webhooks/line/bot - Enable Use webhook
- Copy the Channel Access Token and Channel Secret
LINE_CHANNEL_ACCESS_TOKEN=your_line_channel_access_token
LINE_CHANNEL_SECRET=your_line_channel_secret
Account linking connects LINE users to Daslab accounts. When a user taps "Link Account" in the LINE chat, they're redirected to your server to authenticate.
Telegram Bot
Used for: Telegram chat integration (webhook-based, not OAuth)- Create a bot via @BotFather on Telegram
- Copy the bot token
- Register the webhook by running:
bun run scripts/telegram-setup.ts
This reads PUBLIC_BASE_URL from your environment and registers the webhook URL at https://yourdomain.com/webhooks/telegram/bot.
TELEGRAM_BOT_TOKEN=your_bot_token
TELEGRAM_WEBHOOK_SECRET=your_webhook_secret # optional, for verification
4. Webhook URLs
Several providers deliver real-time events via webhooks. These URLs must be registered in each provider's dashboard. All are served by the Daslab server automatically — you just need to tell each provider where to send events.
| Provider | Webhook URL | Where to configure |
|---|---|---|
| Slack | https://yourdomain.com/webhooks/slack/events | Slack App → Event Subscriptions → Request URL |
| Canva | https://yourdomain.com/webhooks/canva/default | Canva Developer Portal → Integration → Webhook URL |
| Telegram | https://yourdomain.com/webhooks/telegram/bot | Auto-registered via bun run scripts/telegram-setup.ts |
| LINE | https://yourdomain.com/webhooks/line/bot | LINE Developers Console → Messaging API → Webhook URL |
| Vapi | https://yourdomain.com/webhooks/vapi/calls | Vapi Dashboard → Assistant → Server URL |
| 17TRACK | https://yourdomain.com/webhooks/17track/events?type=package | 17TRACK API Console → Settings → Webhook URL |
5. Database Setup
Run the server once with your DATABASE_URL set — it automatically runs migrations on startup:
bun run src/index.ts
Migrations are defined in server/src/db.ts in the migrate() function and run sequentially. The server tracks which migrations have been applied and only runs new ones.
6. iOS Client Configuration
The iOS app connects to Daslab Cloud by default. To connect to your own instance:
- Open Profile in the app (avatar in top-right)
- Under Server Accounts, tap Add Server
- Enter your server URL (e.g.
https://yourdomain.com) - Sign in with your credentials on the self-hosted server
Multi-Server Support
The iOS app supports simultaneous connections to multiple Daslab servers. Organizations from all connected servers appear in a single merged list, grouped by server. When you navigate into an org, API calls automatically route to the correct server.
- Each server has its own auth token stored in Keychain (isolated per server)
- The share extension works with orgs from any connected server
- Swipe to delete a server account from Profile to disconnect
Building from Source
If building the iOS app from source, update the default server URL in:
Daslab/Services/ServerConfig.swift—defaultURLDaslabShare/SharedDataService.swift—defaultServerURL
7. CLI Configuration
The CLI connects to https://daslab.run by default. To use your own server:
export DASLAB_API_URL=https://yourdomain.com
Or pass it per-command:
DASLAB_API_URL=https://yourdomain.com daslab jobs list
If building the CLI from source, the default is set in cli-rust/src/api.rs.
8. Optional Services
These services enhance functionality but aren't required for core operation:
| Service | Env Var | Purpose |
|---|---|---|
| E2B | E2B_API_KEY | Code execution sandbox |
| Tavily | TAVILY_API_KEY | Web search for AI |
| Brave Search | BRAVE_SEARCH_API_KEY | Web search (alternative) |
| Deepgram | DEEPGRAM_API_KEY | Speech-to-text transcription |
| LiveKit | LIVEKIT_API_KEY, LIVEKIT_API_SECRET, LIVEKIT_WS_URL | Real-time voice/video |
| Airtop | AIRTOP_API_KEY | Browser automation |
| Google Maps | GOOGLE_MAPS_API_KEY | Geocoding and maps |
| Brandfetch | BRANDFETCH_CLIENT_ID | Company logo/brand lookup |
9. Deployment Checklist
Use this checklist when setting up a new instance or migrating to a new domain:
Infrastructure
- [ ] Server running with Bun
- [ ] PostgreSQL database provisioned
- [ ] Redis instance provisioned
- [ ]
PUBLIC_BASE_URLset to your domain - [ ] DNS records pointing to your server
- [ ] SSL/TLS configured (most PaaS handle this automatically)
File Storage
- [ ] Cloudflare R2 buckets created (user uploads + releases)
- [ ] R2 custom domains configured (
tmp.andreleases.subdomains) - [ ] R2 credentials set in environment variables
Authentication Providers
- [ ] GitHub OAuth App — callback URL updated
- [ ] Google OAuth — both redirect URIs added, consent screen configured
- [ ] Apple Sign-In — bundle ID matches
APPLE_CLIENT_ID - [ ] Figma — callback URL updated (if using Figma integration)
- [ ] Slack — redirect URL updated, event subscription URL updated (if using Slack)
- [ ] Canva — callback URL and webhook URL updated (if using Canva)
- [ ] Xero — redirect URI updated (if using Xero integration)
- [ ] Upwork — callback URL updated (if using Upwork integration)
Webhooks
- [ ] Telegram — webhook registered via
bun run scripts/telegram-setup.ts - [ ] LINE — webhook URL updated in LINE Developers Console
- [ ] Slack — event subscription request URL updated
- [ ] Vapi — server URL updated on each assistant
- [ ] 17TRACK — webhook URL updated in API console
- [ ] Canva — webhook URL updated in developer portal
- [ ]
server/slack-manifest.yml— hardcoded URLs updated to your domain (if using manifest)
- [ ] Resend API key set
- [ ] Domain verified in Resend (SPF, DKIM, DMARC records)
- [ ]
FROM_EMAILandCONTACT_EMAILset to your domain
iOS App
- [ ] Server URL configured (either in-app or compiled default)
- [ ] Push notification credentials set (
APNS_*variables)
CLI
- [ ]
DASLAB_API_URLset or compiled with correct default
10. Upgrading
The server is stateless — deploy new code and restart. Database migrations run automatically on startup.
For zero-downtime OAuth domain migration:
- Add the new callback URLs to each OAuth provider dashboard (keep the old ones)
- Deploy the server with the new
PUBLIC_BASE_URL - Verify all OAuth flows work with the new domain
- Remove the old callback URLs from each provider dashboard
This ensures no authentication flow breaks during the transition.
11. Troubleshooting
OAuth callback errors
If OAuth redirects fail after a domain change, the most common cause is a mismatch between PUBLIC_BASE_URL and the callback URLs registered with the provider. Check:
# What the server thinks its URL is
curl https://yourdomain.com/health | jq '.apiUrl'
Compare this with the redirect URIs configured in each provider's dashboard.
File uploads returning 404
Ensure the R2 custom domain is configured and propagated:
curl -I https://tmp.yourdomain.com
# Should return 200 or 403 (bucket exists), not DNS errors
Push notifications not delivered
Verify your APNs credentials:
APNS_BUNDLE_IDmust match the iOS app's bundle identifier exactly- The APNs key must be associated with the correct Team ID
- The key must have the Apple Push Notifications service (APNs) capability enabled
Emails not sending
Check that your domain is fully verified in Resend. All three DNS records (SPF, DKIM, DMARC) must be configured correctly. The sender address is configured via the FROM_EMAIL environment variable.