Skip to content

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.md

This 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 variables
ENV NODE_ENV=production
ENV AFFINE_CONFIG_PATH=/root/.affine/config
ENV DATABASE_URL=postgres://user:password@host:5432/affine
# Expose the Affine port
EXPOSE 3010
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:3010/api/healthz || exit 1
# Start Affine
CMD ["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 dependencies
RUN apk add --no-cache curl postgresql-client
# Set working directory
WORKDIR /app
# Set production environment variables
ENV NODE_ENV=production
ENV AFFINE_CONFIG_PATH=/root/.affine/config
ENV AFFINE_SERVER_HOST=0.0.0.0
ENV AFFINE_SERVER_PORT=3010
# Create necessary directories
RUN mkdir -p /root/.affine/config /root/.affine/storage
# Copy custom configuration if needed
# COPY config.json /root/.affine/config/
# Expose the application port
EXPOSE 3010
# Health check for monitoring
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:3010/api/healthz || exit 1
# Start the application
CMD ["node", "dist/index.js"]

Multi-Stage Dockerfile (Optimized)

For smaller image sizes and better security, use a multi-stage build:

# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Production stage
FROM ghcr.io/toeverything/affine-graphql:stable
# Copy only necessary files from builder
COPY --from=builder /app/node_modules ./node_modules
# Set environment variables
ENV NODE_ENV=production
ENV AFFINE_CONFIG_PATH=/root/.affine/config
ENV AFFINE_SERVER_HOST=0.0.0.0
ENV AFFINE_SERVER_PORT=3010
# Create directories for persistent storage
RUN mkdir -p /root/.affine/config /root/.affine/storage /root/.affine/blobs
# Expose port
EXPOSE 3010
# Health check
HEALTHCHECK --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:

    Step 1: Prepare Your GitHub Repository

    1. Create a new GitHub repository for your Affine deployment
    2. Add your Dockerfile to the root directory of the repository
    3. (Optional) Add a .dockerignore file to exclude unnecessary files from the build:
    .git
    .gitignore
    README.md
    node_modules
    *.log
    .DS_Store
    1. Commit and push your changes to GitHub:
    Terminal window
    git add Dockerfile .dockerignore
    git commit -m "Add Affine Dockerfile configuration"
    git push origin main

    Step 2: Create a New App in Klutch.sh

    1. Navigate to the Klutch.sh dashboard
    2. Click on “Create New App” or “New Project”
    3. Select GitHub as your git source
    4. Authorize Klutch.sh to access your GitHub repositories if not already done
    5. Select the repository containing your Affine Dockerfile
    6. Choose the branch you want to deploy (e.g., main or production)

    Step 3: Configure Build Settings

    Klutch.sh will automatically detect your Dockerfile in the root directory. No manual configuration is needed for Docker detection.

    1. Verify that Klutch.sh has detected your Dockerfile (this will be shown in the build configuration)
    2. If your Dockerfile is in a subdirectory, ensure it’s moved to the root or adjust your repository structure

    Step 4: Configure the Application Port

    Affine runs on port 3010 by default. Configure this in Klutch.sh:

    1. In the app settings, locate the “Port Configuration” or “Network Settings” section
    2. Set the internal port to 3010
    3. Select HTTP as the traffic type (not TCP)
    4. Klutch.sh will automatically route external traffic to your internal port

    Step 5: Attach Persistent Volumes

    Affine requires persistent storage for configuration, user data, and uploaded files. Create and attach volumes:

    1. In the Klutch.sh dashboard, navigate to “Volumes” or “Storage”
    2. Create a new persistent volume with the following configuration:
      • Mount path: /root/.affine/storage
      • Size: 10 GB (or more depending on your needs)
    3. Create another volume for configuration:
      • Mount path: /root/.affine/config
      • Size: 1 GB
    4. Create a volume for blob storage (file uploads):
      • Mount path: /root/.affine/blobs
      • Size: 20 GB (adjust based on expected usage)

    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:

    • NODE_ENV - Set to production
    • DATABASE_URL - PostgreSQL connection string (format: postgres://username:password@host:port/database)
    • AFFINE_SERVER_HOST - Set to 0.0.0.0 to accept external connections
    • AFFINE_SERVER_PORT - Set to 3010
    • AFFINE_CONFIG_PATH - Set to /root/.affine/config

    Optional Environment Variables:

    • JWT_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 notifications
    • MAILER_PORT - SMTP port
    • MAILER_USER - SMTP username
    • MAILER_PASSWORD - SMTP password
    • MAILER_SENDER - Email sender address

    Security Best Practices:

    • 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

    Step 7: Configure Database (PostgreSQL)

    Affine requires a PostgreSQL database for production deployments:

    1. Set up a PostgreSQL database (use a managed service or deploy on Klutch.sh)
    2. Create a database for Affine:
    CREATE DATABASE affine;
    CREATE USER affine_user WITH PASSWORD 'secure_password';
    GRANT ALL PRIVILEGES ON DATABASE affine TO affine_user;
    1. Set the DATABASE_URL environment variable in Klutch.sh:
    postgres://affine_user:secure_password@your-postgres-host:5432/affine

    For detailed database setup instructions, refer to the Klutch.sh Database Guides.

    Step 8: Deploy the Application

    1. Review all your configuration settings

    2. Click “Deploy” or “Create App”

    3. 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
    4. Monitor the build logs in real-time to ensure successful deployment

    5. Once deployment is complete, Klutch.sh will provide you with a URL in the format: example-app.klutch.sh

    Step 9: Verify Deployment

    1. Access your Affine instance at https://your-app-name.klutch.sh
    2. Complete the initial setup wizard
    3. Create your first workspace
    4. Test file uploads and document creation
    5. Verify that data persists across container restarts

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:

Terminal window
node dist/index.js --max-old-space-size=4096

Set 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 command
  • NIXPACKS_INSTALL_CMD - Custom install command

Environment Variables Reference

Core Configuration

VariableDescriptionRequiredDefault
NODE_ENVNode environmentYesproduction
DATABASE_URLPostgreSQL connection stringYes-
AFFINE_SERVER_HOSTServer host addressNo0.0.0.0
AFFINE_SERVER_PORTServer portNo3010
AFFINE_CONFIG_PATHConfiguration directory pathNo/root/.affine/config

Storage Configuration

VariableDescriptionRequiredDefault
AFFINE_STORAGE_PATHMain storage pathNo/root/.affine/storage
AFFINE_BLOB_STORAGE_PATHBlob storage pathNo/root/.affine/blobs

Authentication & Security

VariableDescriptionRequiredDefault
JWT_SECRETJWT signing secretRecommended-
SESSION_SECRETSession encryption secretRecommended-

Email Configuration

VariableDescriptionRequiredDefault
MAILER_HOSTSMTP server hostNo-
MAILER_PORTSMTP server portNo587
MAILER_USERSMTP usernameNo-
MAILER_PASSWORDSMTP passwordNo-
MAILER_SENDERFrom email addressNo-

Optional Features

VariableDescriptionRequiredDefault
REDIS_URLRedis connection stringNo-
ENABLE_TELEMETRYEnable usage telemetryNofalse

Persistent Storage Best Practices

Volume Configuration

Affine requires persistent storage for several purposes:

  1. Configuration Storage (/root/.affine/config)

    • Stores application configuration
    • Minimal size required (1-2 GB)
    • Critical for maintaining settings across deployments
  2. Data Storage (/root/.affine/storage)

    • Stores workspace data and documents
    • Size depends on usage (start with 10 GB)
    • Can be expanded as needed
  3. 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:

  1. Use Klutch.sh’s built-in volume snapshot feature
  2. Schedule regular backups of your PostgreSQL database
  3. Export workspaces periodically through Affine’s export feature
  4. Store backups in a separate location or cloud storage

Scaling and Production Optimization

Performance Optimization

  1. Database Connection Pooling

    • Use connection pooling for PostgreSQL
    • Set appropriate max connections in DATABASE_URL
  2. Redis Caching

    • Configure Redis for session storage and caching
    • Significantly improves performance under load
  3. 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:

  1. Check application logs in the Klutch.sh dashboard
  2. Monitor CPU and memory usage
  3. Track storage utilization
  4. Set up health check endpoints
  5. Monitor database performance

High Availability

For production deployments requiring high availability:

  1. Scale horizontally by increasing instance count
  2. Use a managed PostgreSQL service with replication
  3. Configure Redis for session persistence
  4. Implement database backups and disaster recovery
  5. Consider setting up a staging environment

Troubleshooting Common Issues

Application Won’t Start

Symptoms: Container keeps restarting, health checks fail

Solutions:

  1. Check that DATABASE_URL is correctly formatted
  2. Verify PostgreSQL is accessible from the container
  3. Ensure all required environment variables are set
  4. Review application logs in Klutch.sh dashboard
  5. Verify the internal port is set to 3010

Database Connection Errors

Symptoms: “Failed to connect to database” errors

Solutions:

  1. Verify PostgreSQL is running and accessible
  2. Check database credentials in DATABASE_URL
  3. Ensure database user has proper permissions
  4. Test database connectivity from a debug container
  5. Check firewall rules and network configuration

File Upload Failures

Symptoms: Cannot upload files or attachments

Solutions:

  1. Verify blob storage volume is properly mounted at /root/.affine/blobs
  2. Check that the volume has sufficient space
  3. Ensure AFFINE_BLOB_STORAGE_PATH is correctly set
  4. Verify directory permissions within the container
  5. Check application logs for specific error messages

Session/Authentication Issues

Symptoms: Users logged out frequently, session errors

Solutions:

  1. Ensure JWT_SECRET is set and consistent
  2. Configure Redis for session storage in production
  3. Check that SESSION_SECRET is properly configured
  4. Verify cookie settings and HTTPS configuration

Slow Performance

Symptoms: Slow page loads, delayed responses

Solutions:

  1. Check CPU and memory usage in Klutch.sh dashboard
  2. Scale up instance resources (CPU/RAM)
  3. Configure Redis caching
  4. Optimize PostgreSQL configuration
  5. Review database query performance
  6. Consider horizontal scaling with multiple instances

Security Best Practices

Secure Your Deployment

  1. Use Strong Secrets

    • Generate cryptographically strong secrets for JWT_SECRET and SESSION_SECRET
    • Use a password manager or secret generator
    • Never commit secrets to version control
  2. 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
  3. 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
  4. 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
  5. 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:

  1. 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
  2. Prepare Klutch.sh Deployment

    • Set up PostgreSQL on Klutch.sh or a managed service
    • Create persistent volumes for storage
    • Configure environment variables
  3. Import Your Data

    • Restore your PostgreSQL database backup
    • Upload files to the blob storage volume
    • Import workspaces into the new Affine instance
  4. 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:

  1. Navigate to the app settings in the Klutch.sh dashboard
  2. Add your custom domain (e.g., affine.example.com)
  3. 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
  4. Klutch.sh automatically provisions and manages SSL/TLS certificates
  5. Update the AFFINE_SERVER_HOST environment variable if needed

For detailed instructions, see the Custom Domains Guide.


Cost Optimization

Optimize Your Affine Deployment Costs

  1. Right-Size Resources

    • Start with minimum resources and scale as needed
    • Monitor actual usage and adjust accordingly
  2. Storage Optimization

    • Regularly clean up unused files and data
    • Implement data retention policies
    • Consider object storage for large files
  3. Database Optimization

    • Use appropriate PostgreSQL instance size
    • Implement proper indexing
    • Regular database maintenance and vacuuming
  4. Caching Strategy

    • Implement Redis for caching
    • Reduce database queries
    • Cache static assets effectively

Advanced Configuration

Redis Integration

For improved performance and session management:

  1. Deploy Redis on Klutch.sh or use a managed Redis service
  2. Set the REDIS_URL environment variable:
redis://your-redis-host:6379
  1. Affine will automatically use Redis for caching and session storage

Email Notifications

Configure SMTP for email notifications:

  1. Choose an SMTP provider (SendGrid, Mailgun, Amazon SES, etc.)
  2. Set the email environment variables in Klutch.sh
  3. Test email functionality with password resets and invitations

Reverse Proxy Configuration

If running Affine behind a reverse proxy:

  1. Ensure proper headers are forwarded (X-Forwarded-For, X-Forwarded-Proto)
  2. Configure Affine to trust proxy headers
  3. 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?

  1. Update the Docker image tag in your Dockerfile to the latest version
  2. Push changes to GitHub
  3. Klutch.sh will automatically rebuild and redeploy
  4. 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:

  1. Increase the volume size in the dashboard
  2. The change is applied without downtime
  3. Clean up unnecessary files if needed

Can I scale Affine horizontally?

Yes, you can run multiple instances of Affine:

  1. Ensure you’re using PostgreSQL (not SQLite)
  2. Configure Redis for shared session storage
  3. Use shared storage for blobs (object storage recommended)
  4. Increase the instance count in Klutch.sh

How do I back up my data?

  1. Use Klutch.sh volume snapshots for storage volumes
  2. Regularly back up your PostgreSQL database
  3. Export workspaces through Affine’s built-in export feature
  4. Store backups in a secure, separate location

Official Affine Resources

Klutch.sh Resources

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!