Deploying an Affine App
Introduction
Affine is a next-generation open-source workspace and knowledge management platform that combines the best features of Notion, Miro, and Markdown editors into one powerful collaborative tool. Built with privacy-first principles, Affine offers local-first data storage, real-time collaboration, and a flexible workspace for notes, documents, whiteboards, and databases. Whether you’re managing personal knowledge, team documentation, or project workflows, deploying Affine on Klutch.sh provides you with a scalable, secure, and production-ready infrastructure.
This comprehensive guide will walk you through deploying Affine on Klutch.sh using Docker, configuring persistent storage for your data, setting up environment variables, and following best practices for production deployments. Klutch.sh automatically detects your Dockerfile and handles the build process using Nixpacks, making deployment seamless and efficient.
What is Affine?
Affine is an all-in-one workspace application that combines:
- Document editing: Rich text editing with Markdown support
- Whiteboard capabilities: Visual collaboration and diagramming
- Database views: Organize information in tables, kanban boards, and galleries
- Block-based editing: Flexible content organization with drag-and-drop
- Real-time collaboration: Work together with your team in real-time
- Privacy-focused: Self-hostable with local-first architecture
By deploying Affine on Klutch.sh, you get automatic builds, persistent storage, scalability, and the reliability of a modern cloud platform.
Prerequisites
Before you begin deploying Affine on Klutch.sh, ensure you have:
- A Klutch.sh account (sign up here)
- A GitHub repository containing your Affine Dockerfile configuration
- Basic understanding of Docker containerization
- Familiarity with environment variables and configuration management
- (Optional) PostgreSQL database instance for production deployments
Project Structure
A typical Affine deployment repository should have the following structure:
affine-deployment/├── Dockerfile├── .dockerignore├── docker-entrypoint.sh (optional)└── README.mdThis minimal structure allows Klutch.sh to automatically detect your Dockerfile in the root directory and build your Affine application.
Sample Dockerfile for Affine
Klutch.sh automatically detects a Dockerfile if present in the root directory of your repository. Here’s a production-ready Dockerfile for deploying Affine:
Basic Dockerfile
FROM ghcr.io/toeverything/affine-graphql:stable
# Set environment variablesENV NODE_ENV=productionENV AFFINE_CONFIG_PATH=/root/.affine/configENV DATABASE_URL=postgres://user:password@host:5432/affine
# Expose the Affine portEXPOSE 3010
# Health checkHEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ CMD curl -f http://localhost:3010/api/healthz || exit 1
# Start AffineCMD ["node", "dist/index.js"]Production Dockerfile with Custom Configuration
For more control over your deployment, use this enhanced Dockerfile:
FROM ghcr.io/toeverything/affine-graphql:stable
# Install additional dependenciesRUN apk add --no-cache curl postgresql-client
# Set working directoryWORKDIR /app
# Set production environment variablesENV NODE_ENV=productionENV AFFINE_CONFIG_PATH=/root/.affine/configENV AFFINE_SERVER_HOST=0.0.0.0ENV AFFINE_SERVER_PORT=3010
# Create necessary directoriesRUN mkdir -p /root/.affine/config /root/.affine/storage
# Copy custom configuration if needed# COPY config.json /root/.affine/config/
# Expose the application portEXPOSE 3010
# Health check for monitoringHEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ CMD curl -f http://localhost:3010/api/healthz || exit 1
# Start the applicationCMD ["node", "dist/index.js"]Multi-Stage Dockerfile (Optimized)
For smaller image sizes and better security, use a multi-stage build:
# Build stageFROM node:18-alpine AS builder
WORKDIR /app
# Copy package filesCOPY package*.json ./
# Install dependenciesRUN npm ci --only=production
# Production stageFROM ghcr.io/toeverything/affine-graphql:stable
# Copy only necessary files from builderCOPY --from=builder /app/node_modules ./node_modules
# Set environment variablesENV NODE_ENV=productionENV AFFINE_CONFIG_PATH=/root/.affine/configENV AFFINE_SERVER_HOST=0.0.0.0ENV AFFINE_SERVER_PORT=3010
# Create directories for persistent storageRUN mkdir -p /root/.affine/config /root/.affine/storage /root/.affine/blobs
# Expose portEXPOSE 3010
# Health checkHEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ CMD curl -f http://localhost:3010/api/healthz || exit 1
# Run as non-root user for security (if supported)# USER node
CMD ["node", "dist/index.js"]Deploying Affine to Klutch.sh
Follow these detailed steps to deploy your Affine application on Klutch.sh:
- Create a new GitHub repository for your Affine deployment
- Add your Dockerfile to the root directory of the repository
- (Optional) Add a
.dockerignorefile to exclude unnecessary files from the build: - Commit and push your changes to GitHub:
- Navigate to the Klutch.sh dashboard
- Click on “Create New App” or “New Project”
- Select GitHub as your git source
- Authorize Klutch.sh to access your GitHub repositories if not already done
- Select the repository containing your Affine Dockerfile
- Choose the branch you want to deploy (e.g.,
mainorproduction) - Verify that Klutch.sh has detected your Dockerfile (this will be shown in the build configuration)
- If your Dockerfile is in a subdirectory, ensure it’s moved to the root or adjust your repository structure
- In the app settings, locate the “Port Configuration” or “Network Settings” section
- Set the internal port to
3010 - Select HTTP as the traffic type (not TCP)
- Klutch.sh will automatically route external traffic to your internal port
- In the Klutch.sh dashboard, navigate to “Volumes” or “Storage”
- Create a new persistent volume with the following configuration:
- Mount path:
/root/.affine/storage - Size: 10 GB (or more depending on your needs)
- Mount path:
- Create another volume for configuration:
- Mount path:
/root/.affine/config - Size: 1 GB
- Mount path:
- Create a volume for blob storage (file uploads):
- Mount path:
/root/.affine/blobs - Size: 20 GB (adjust based on expected usage)
- Mount path:
NODE_ENV- Set toproductionDATABASE_URL- PostgreSQL connection string (format:postgres://username:password@host:port/database)AFFINE_SERVER_HOST- Set to0.0.0.0to accept external connectionsAFFINE_SERVER_PORT- Set to3010AFFINE_CONFIG_PATH- Set to/root/.affine/configJWT_SECRET- Secret key for JWT token generation (generate a secure random string)REDIS_URL- Redis connection string for caching (format:redis://host:port)AFFINE_STORAGE_PATH- Path for file storage (set to/root/.affine/storage)AFFINE_BLOB_STORAGE_PATH- Path for blob storage (set to/root/.affine/blobs)MAILER_HOST- SMTP host for email notificationsMAILER_PORT- SMTP portMAILER_USER- SMTP usernameMAILER_PASSWORD- SMTP passwordMAILER_SENDER- Email sender address- Never commit sensitive values like database passwords or JWT secrets to your repository
- Use Klutch.sh’s environment variable management to securely store secrets
- Rotate secrets regularly
- Set up a PostgreSQL database (use a managed service or deploy on Klutch.sh)
- Create a database for Affine:
- Set the
DATABASE_URLenvironment variable in Klutch.sh: -
Review all your configuration settings
-
Click “Deploy” or “Create App”
-
Klutch.sh will:
- Clone your repository from GitHub
- Detect the Dockerfile in the root directory
- Build the Docker image using Nixpacks
- Create a container from the image
- Mount your persistent volumes
- Inject environment variables
- Start your application
-
Monitor the build logs in real-time to ensure successful deployment
-
Once deployment is complete, Klutch.sh will provide you with a URL in the format:
example-app.klutch.sh - Access your Affine instance at
https://your-app-name.klutch.sh - Complete the initial setup wizard
- Create your first workspace
- Test file uploads and document creation
- Verify that data persists across container restarts
Step 1: Prepare Your GitHub Repository
.git.gitignoreREADME.mdnode_modules*.log.DS_Storegit add Dockerfile .dockerignoregit commit -m "Add Affine Dockerfile configuration"git push origin mainStep 2: Create a New App in Klutch.sh
Step 3: Configure Build Settings
Klutch.sh will automatically detect your Dockerfile in the root directory. No manual configuration is needed for Docker detection.
Step 4: Configure the Application Port
Affine runs on port 3010 by default. Configure this in Klutch.sh:
Step 5: Attach Persistent Volumes
Affine requires persistent storage for configuration, user data, and uploaded files. Create and attach volumes:
Note: You can only specify the mount path and size for volumes in Klutch.sh. Volume names are managed automatically by the platform.
For more details on managing volumes, see the Klutch.sh Volumes Guide.
Step 6: Configure Environment Variables
Set up the required environment variables in the Klutch.sh dashboard:
Required Environment Variables:
Optional Environment Variables:
Security Best Practices:
Step 7: Configure Database (PostgreSQL)
Affine requires a PostgreSQL database for production deployments:
CREATE DATABASE affine;CREATE USER affine_user WITH PASSWORD 'secure_password';GRANT ALL PRIVILEGES ON DATABASE affine TO affine_user;postgres://affine_user:secure_password@your-postgres-host:5432/affineFor detailed database setup instructions, refer to the Klutch.sh Database Guides.
Step 8: Deploy the Application
Step 9: Verify Deployment
Customizing Your Affine Deployment
Changing the Start Command
If you need to customize how Affine starts, you can set buildtime or runtime environment variables for Nixpacks:
Runtime Environment Variables:
NIXPACKS_START_CMD- Override the default start command
Example:
node dist/index.js --max-old-space-size=4096Set this in the Klutch.sh dashboard under environment variables.
Modifying the Build Process
For custom build steps, use Nixpacks environment variables:
Buildtime Environment Variables:
NIXPACKS_BUILD_CMD- Custom build commandNIXPACKS_INSTALL_CMD- Custom install command
Environment Variables Reference
Core Configuration
| Variable | Description | Required | Default |
|---|---|---|---|
NODE_ENV | Node environment | Yes | production |
DATABASE_URL | PostgreSQL connection string | Yes | - |
AFFINE_SERVER_HOST | Server host address | No | 0.0.0.0 |
AFFINE_SERVER_PORT | Server port | No | 3010 |
AFFINE_CONFIG_PATH | Configuration directory path | No | /root/.affine/config |
Storage Configuration
| Variable | Description | Required | Default |
|---|---|---|---|
AFFINE_STORAGE_PATH | Main storage path | No | /root/.affine/storage |
AFFINE_BLOB_STORAGE_PATH | Blob storage path | No | /root/.affine/blobs |
Authentication & Security
| Variable | Description | Required | Default |
|---|---|---|---|
JWT_SECRET | JWT signing secret | Recommended | - |
SESSION_SECRET | Session encryption secret | Recommended | - |
Email Configuration
| Variable | Description | Required | Default |
|---|---|---|---|
MAILER_HOST | SMTP server host | No | - |
MAILER_PORT | SMTP server port | No | 587 |
MAILER_USER | SMTP username | No | - |
MAILER_PASSWORD | SMTP password | No | - |
MAILER_SENDER | From email address | No | - |
Optional Features
| Variable | Description | Required | Default |
|---|---|---|---|
REDIS_URL | Redis connection string | No | - |
ENABLE_TELEMETRY | Enable usage telemetry | No | false |
Persistent Storage Best Practices
Volume Configuration
Affine requires persistent storage for several purposes:
-
Configuration Storage (
/root/.affine/config)- Stores application configuration
- Minimal size required (1-2 GB)
- Critical for maintaining settings across deployments
-
Data Storage (
/root/.affine/storage)- Stores workspace data and documents
- Size depends on usage (start with 10 GB)
- Can be expanded as needed
-
Blob Storage (
/root/.affine/blobs)- Stores uploaded files and attachments
- Largest storage requirement
- Recommended: 20-50 GB or more
Backup Strategy
Regularly back up your persistent volumes:
- Use Klutch.sh’s built-in volume snapshot feature
- Schedule regular backups of your PostgreSQL database
- Export workspaces periodically through Affine’s export feature
- Store backups in a separate location or cloud storage
Scaling and Production Optimization
Performance Optimization
-
Database Connection Pooling
- Use connection pooling for PostgreSQL
- Set appropriate max connections in
DATABASE_URL
-
Redis Caching
- Configure Redis for session storage and caching
- Significantly improves performance under load
-
Resource Allocation
- Allocate sufficient CPU and memory based on user count
- Recommended minimum: 2 CPU cores, 4 GB RAM
- Scale up as your user base grows
Monitoring
Monitor your Affine deployment:
- Check application logs in the Klutch.sh dashboard
- Monitor CPU and memory usage
- Track storage utilization
- Set up health check endpoints
- Monitor database performance
High Availability
For production deployments requiring high availability:
- Scale horizontally by increasing instance count
- Use a managed PostgreSQL service with replication
- Configure Redis for session persistence
- Implement database backups and disaster recovery
- Consider setting up a staging environment
Troubleshooting Common Issues
Application Won’t Start
Symptoms: Container keeps restarting, health checks fail
Solutions:
- Check that
DATABASE_URLis correctly formatted - Verify PostgreSQL is accessible from the container
- Ensure all required environment variables are set
- Review application logs in Klutch.sh dashboard
- Verify the internal port is set to
3010
Database Connection Errors
Symptoms: “Failed to connect to database” errors
Solutions:
- Verify PostgreSQL is running and accessible
- Check database credentials in
DATABASE_URL - Ensure database user has proper permissions
- Test database connectivity from a debug container
- Check firewall rules and network configuration
File Upload Failures
Symptoms: Cannot upload files or attachments
Solutions:
- Verify blob storage volume is properly mounted at
/root/.affine/blobs - Check that the volume has sufficient space
- Ensure
AFFINE_BLOB_STORAGE_PATHis correctly set - Verify directory permissions within the container
- Check application logs for specific error messages
Session/Authentication Issues
Symptoms: Users logged out frequently, session errors
Solutions:
- Ensure
JWT_SECRETis set and consistent - Configure Redis for session storage in production
- Check that
SESSION_SECRETis properly configured - Verify cookie settings and HTTPS configuration
Slow Performance
Symptoms: Slow page loads, delayed responses
Solutions:
- Check CPU and memory usage in Klutch.sh dashboard
- Scale up instance resources (CPU/RAM)
- Configure Redis caching
- Optimize PostgreSQL configuration
- Review database query performance
- Consider horizontal scaling with multiple instances
Security Best Practices
Secure Your Deployment
-
Use Strong Secrets
- Generate cryptographically strong secrets for
JWT_SECRETandSESSION_SECRET - Use a password manager or secret generator
- Never commit secrets to version control
- Generate cryptographically strong secrets for
-
Database Security
- Use strong, unique passwords for database users
- Restrict database access to only necessary hosts
- Enable SSL/TLS for database connections
- Regularly update and patch PostgreSQL
-
Network Security
- Use HTTPS for all connections (Klutch.sh provides this automatically)
- Configure proper CORS settings if using custom domains
- Implement rate limiting for API endpoints
-
Container Security
- Keep base images updated
- Run containers as non-root users when possible
- Minimize installed packages in Docker images
- Regularly scan images for vulnerabilities
-
Access Control
- Implement strong password policies
- Enable two-factor authentication (2FA) when available
- Regularly review user access and permissions
- Monitor authentication logs
Migrating to Klutch.sh
From Self-Hosted Affine
If you’re migrating from an existing self-hosted Affine instance:
-
Export Your Data
- Export all workspaces from your current Affine instance
- Back up your PostgreSQL database using
pg_dump - Download all uploaded files and blobs
-
Prepare Klutch.sh Deployment
- Set up PostgreSQL on Klutch.sh or a managed service
- Create persistent volumes for storage
- Configure environment variables
-
Import Your Data
- Restore your PostgreSQL database backup
- Upload files to the blob storage volume
- Import workspaces into the new Affine instance
-
Verify Migration
- Test all functionality
- Verify all files and documents are accessible
- Update DNS and custom domains if applicable
Custom Domains and SSL
To use a custom domain with your Affine deployment:
- Navigate to the app settings in the Klutch.sh dashboard
- Add your custom domain (e.g.,
affine.example.com) - Configure your DNS provider:
- Add a CNAME record pointing to your Klutch.sh app URL
- Or add an A record to the IP address provided by Klutch.sh
- Klutch.sh automatically provisions and manages SSL/TLS certificates
- Update the
AFFINE_SERVER_HOSTenvironment variable if needed
For detailed instructions, see the Custom Domains Guide.
Cost Optimization
Optimize Your Affine Deployment Costs
-
Right-Size Resources
- Start with minimum resources and scale as needed
- Monitor actual usage and adjust accordingly
-
Storage Optimization
- Regularly clean up unused files and data
- Implement data retention policies
- Consider object storage for large files
-
Database Optimization
- Use appropriate PostgreSQL instance size
- Implement proper indexing
- Regular database maintenance and vacuuming
-
Caching Strategy
- Implement Redis for caching
- Reduce database queries
- Cache static assets effectively
Advanced Configuration
Redis Integration
For improved performance and session management:
- Deploy Redis on Klutch.sh or use a managed Redis service
- Set the
REDIS_URLenvironment variable:
redis://your-redis-host:6379- Affine will automatically use Redis for caching and session storage
Email Notifications
Configure SMTP for email notifications:
- Choose an SMTP provider (SendGrid, Mailgun, Amazon SES, etc.)
- Set the email environment variables in Klutch.sh
- Test email functionality with password resets and invitations
Reverse Proxy Configuration
If running Affine behind a reverse proxy:
- Ensure proper headers are forwarded (
X-Forwarded-For,X-Forwarded-Proto) - Configure Affine to trust proxy headers
- Set appropriate CORS headers if needed
Development vs Production
Development Deployment
For testing and development:
- Use SQLite instead of PostgreSQL (lighter weight)
- Smaller volume sizes
- Disable email notifications
- Use minimal resource allocation
- Enable debug logging
Production Deployment
For production use:
- Use PostgreSQL with replication
- Adequate volume sizes with room for growth
- Configure email notifications
- Allocate sufficient resources (2+ CPU, 4+ GB RAM)
- Enable monitoring and health checks
- Implement backup strategy
- Use Redis for caching
- Configure custom domain with SSL
Frequently Asked Questions
Can I use SQLite instead of PostgreSQL?
Yes, for small deployments or testing, you can use SQLite by omitting the DATABASE_URL environment variable. However, PostgreSQL is strongly recommended for production use due to better performance, reliability, and concurrent user support.
How do I upgrade Affine?
- Update the Docker image tag in your Dockerfile to the latest version
- Push changes to GitHub
- Klutch.sh will automatically rebuild and redeploy
- Your persistent volumes ensure data is preserved
What happens if I run out of storage?
Monitor your volume usage in the Klutch.sh dashboard. When approaching capacity:
- Increase the volume size in the dashboard
- The change is applied without downtime
- Clean up unnecessary files if needed
Can I scale Affine horizontally?
Yes, you can run multiple instances of Affine:
- Ensure you’re using PostgreSQL (not SQLite)
- Configure Redis for shared session storage
- Use shared storage for blobs (object storage recommended)
- Increase the instance count in Klutch.sh
How do I back up my data?
- Use Klutch.sh volume snapshots for storage volumes
- Regularly back up your PostgreSQL database
- Export workspaces through Affine’s built-in export feature
- Store backups in a secure, separate location
Resources and Links
Official Affine Resources
Klutch.sh Resources
- Klutch.sh Quick Start Guide
- Volumes Guide
- Builds Guide
- Deployments Guide
- Custom Domains Guide
- Database Guides
Community and Support
Conclusion
Deploying Affine on Klutch.sh provides you with a powerful, scalable, and secure workspace solution for your team or personal use. By following this guide, you’ve learned how to:
- Set up a production-ready Affine deployment with Docker
- Configure persistent storage for data and files
- Manage environment variables and secrets securely
- Optimize performance with PostgreSQL and Redis
- Implement security best practices
- Scale your deployment as your needs grow
Klutch.sh’s automatic Dockerfile detection, Nixpacks build system, and managed infrastructure make deploying and maintaining Affine straightforward and reliable. With proper configuration of persistent volumes, database connectivity, and environment variables, you’ll have a robust knowledge management platform ready for production use.
For additional help or advanced configurations, refer to the official Affine documentation and Klutch.sh support resources linked above. Happy building with Affine on Klutch.sh!