SELF-HOSTING

The Ultimate Guide: Self-Hosting Forem (Production) on macOS Apple Silicon using OrbStack

If you have tried deploying Forem (the open-source software powering DEV.to) on a Mac with an M1, M2, M3 or M4 chip using Docker Desktop, you likely faced a nightmare of errors: nokogiri build failures, architecture mismatches (amd64 vs arm64), file permission issues, and unbearable slowness.

The solution isn’t to fight Docker Desktop, but to bypass the architectural friction entirely. In this guide, we will deploy a production-ready Forem instance inside a lightweight Ubuntu Virtual Machine managed by OrbStack.

This approach offers native Linux performance, eliminates file system bridge slowness, and resolves the infamous /usr/bin/bash and permission errors.

AI & Automation (vnROM)

Prerequisites

  1. OrbStack: A fast, light, and easy way to run Docker containers and Linux machines on macOS. Download here.
  2. VS Code (Optional but recommended): For easy file editing inside the VM.

Setting up the Environment

We will create a dedicated Ubuntu environment. This ensures your Forem instance runs on a standard Linux file system (ext4), avoiding the performance penalties of macOS file sharing.

1. Create the Virtual Machine

Open your Mac Terminal and run:

# Create an Ubuntu 24.04 (Noble) machine named 'forem-server'
orb create ubuntu:noble forem-server

2. Enter the VM

From now on, all commands must be run inside this shell:

orb -m forem-server

3. Install Docker Engine (Inside the VM)

Although OrbStack provides Docker, installing the native Docker engine inside the VM ensures a completely isolated production environment.

# Update system
sudo apt-get update && sudo apt-get upgrade -y

# Install Docker and Compose
sudo apt-get install -y docker.io docker-compose-v2

# Add your user to the docker group (to run commands without sudo)
sudo usermod -aG docker $USER
newgrp docker

Getting the Code & Patching Legacy Issues

Forem’s default configuration currently has some legacy settings that break on modern systems. We will patch them automatically.

1. Clone the Repository

Important: Clone this into the VM’s home directory. Do not clone it on your Mac and mount it.

cd ~
git clone https://github.com/forem/forem.git
cd forem

2. Patch the Containerfile

We need to fix two critical issues in the default Dockerfile:

  1. NodeSource Error: The old Node.js repository configuration causes 404 errors during build.
  2. Bash Path Error: Forem scripts expect /usr/bin/bash, but Debian/Ubuntu uses /bin/bash.

Run these sed commands to patch the file automatically:

# Fix 1: Remove the broken NodeSource list before running apt-get update
sed -i 's/apt update/rm -f \/etc\/apt\/sources.list.d\/nodesource.list \&\& apt update/g' Containerfile

# Fix 2: Create a symlink for bash to satisfy Forem's scripts
sed -i '/apt-get install -yq --no-install-recommends \\/a \      ln -s /bin/bash /usr/bin/bash && \\' Containerfile

Production Configuration

We will skip the development setup and go straight to a lean Production setup.

1. Configure .env

Create a new .env file:

nano .env

Paste the following configuration. This is optimized for a standalone production instance.

# --- APP IDENTITY ---
# Change this to your real domain (e.g., ai.addrom.com)
APP_DOMAIN="your-domain.com"
APP_PROTOCOL="https://"
COMMUNITY_NAME="addROM Community"
DEFAULT_EMAIL="[email protected]" # Must match SMTP_USER_NAME

# --- SECRETS (Generate new random strings for these!) ---
FOREM_OWNER_SECRET="long_random_string_1"
SECRET_KEY_BASE="long_random_string_2"

# --- DATABASE & REDIS ---
# Note: We use service names 'postgres' and 'redis', NOT 'localhost'
DATABASE_URL="postgresql://forem:forem@postgres:5432/addrom_forem"
DATABASE_NAME="addrom_forem"
DATABASE_POOL_SIZE=5
REDIS_URL="redis://redis:6379"
REDIS_SESSIONS_URL="redis://redis:6379"
REDIS_SIDEKIQ_URL="redis://redis:6379"

# --- ENVIRONMENT ---
RAILS_ENV="production"
NODE_ENV="production"
RAILS_SERVE_STATIC_FILES="true"
RAILS_LOG_TO_STDOUT="true"

# --- SMTP CONFIGURATION (Example: Larksuite/Gmail) ---
# We use port 587 (STARTTLS) for maximum stability
SMTP_ADDRESS="smtp.larksuite.com"
SMTP_PORT="587"
SMTP_DOMAIN="your-domain.com"
SMTP_USER_NAME="[email protected]"
SMTP_PASSWORD="your_app_password"
SMTP_AUTHENTICATION="login"
SMTP_TLS=false
SMTP_ENABLE_STARTTLS_AUTO=true
# Optional: Bypass SSL verification if you encounter certificate errors
SMTP_OPENSSL_VERIFY_MODE="none"

# --- SECURITY ---
FORCE_SSL_IN_RAILS="true"

2. Configure docker-compose.yml

Delete the existing file and replace it with this clean Production version.

Crucial: Note the target: production line. This ensures the code is copied inside the image, fixing the “Gemfile not found” error.

nano docker-compose.yml:

services:
  postgres:
    image: postgres:13
    container_name: forem_postgres
    restart: always
    environment:
      POSTGRES_USER: forem
      POSTGRES_PASSWORD: forem
      POSTGRES_DB: addrom_forem
    volumes:
      - db_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "forem"]
      interval: 10s
      timeout: 5s
      retries: 5
    env_file:
      - .env

  redis:
    image: redis:6.2-alpine
    container_name: forem_redis
    restart: always
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    env_file:
      - .env

  web:
    build:
      context: .
      dockerfile: Containerfile
      target: production # <--- ESSENTIAL
    container_name: forem_web
    restart: always
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    env_file:
      - .env
    ports:
      - "3000:3000"
    command: bundle exec rails s -b 0.0.0.0
    volumes:
      # Persist uploads and assets
      - uploads_data:/opt/apps/forem/public/uploads
      - assets_data:/opt/apps/forem/public/assets

  sidekiq:
    build:
      context: .
      dockerfile: Containerfile
      target: production
    container_name: forem_sidekiq
    restart: always
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    env_file:
      - .env
    command: bundle exec sidekiq

volumes:
  db_data:
  redis_data:
  uploads_data:
  assets_data:

Build and Initialization

This is where the magic happens. Run these commands sequentially.

1. Build the Docker Images:

docker compose build

2. Initialize the Database:

We use bundle exec to ensure the command runs within the correct Ruby context inside the container.

docker compose run --rm web bundle exec rails db:setup

3. Fix Permissions (Crucial step):

Docker containers often run as a specific user (forem/1000) but volumes might be owned by root. This fixes the Errno::EACCES error when uploading images.

docker compose run --rm -u root web mkdir -p /opt/apps/forem/public/uploads/tmp
docker compose run --rm -u root web chown -R 1000:1000 /opt/apps/forem/public
docker compose run --rm -u root web chmod -R 777 /opt/apps/forem/public

4. Precompile Assets:

Required for CSS and JS to load correctly in production mode.

docker compose run --rm web bundle exec rails assets:precompile

5. Start the Server:

docker compose up -d

Creating the Super Admin (The Reliable Way)

Don’t rely on the registration form initially (SMTP might fail, or you might hit 404s). Create your admin account directly via the Rails Console.

  1. Enter the console:
docker compose exec web bundle exec rails c
  1. Run this Ruby code block:
# Create the user
u = User.new(
  name: "Super Admin", 
  username: "admin", 
  email: "[email protected]", 
  password: "StrongPassword123!", 
  password_confirmation: "StrongPassword123!"
)
u.save!

# Confirm email automatically (Bypass SMTP)
u.confirm

# Grant Super Admin privileges
u.add_role(:admin)
u.add_role(:super_admin)
u.save!
exit

Now navigate to http://<VM-IP-Address>:3000 or http://localhost:3000. You can log in immediately!

Maintenance (Update, Backup, Restore)

Updating Forem

Since we modified the Containerfile, we need to be careful when pulling updates.

# 1. Reset file changes to allow git pull
git checkout Containerfile

# 2. Get latest code
git pull origin main

# 3. Re-apply patches (Run the sed commands from Part 2 again)
# ... [Insert sed commands here] ...

# 4. Rebuild and Migrate
docker compose build
docker compose run --rm web bundle exec rails db:migrate
docker compose run --rm web bundle exec rails assets:precompile
docker compose up -d

Backups

Run these commands to backup your data to the VM’s home directory.

# Database
docker compose exec -t postgres pg_dump -U forem addrom_forem > ~/forem_backup.sql

# Uploads
docker compose exec -t web tar -czf /tmp/uploads.tar.gz -C /opt/apps/forem/public/uploads .
docker cp forem_web:/tmp/uploads.tar.gz ~/uploads_backup.tar.gz

Troubleshooting Cheat Sheet

Error MessageCauseSolution
E: The repository ... nodesource ... does not have a Release fileOld Node.js repo in the base image.Run the sed command to remove nodesource.list in Containerfile.
exec: "/usr/bin/bash": stat /usr/bin/bash: no such filePath mismatch (Debian vs script expectations).Run the sed command to symlink /bin/bash to /usr/bin/bash.
Could not locate GemfileDocker built an empty development image.Ensure target: production is in your docker-compose.yml.
ActiveRecord::ConnectionNotEstablished ... 127.0.0.1Rails is trying to connect to localhost DB.Update .env: DATABASE_URL=postgresql://...@postgres:5432...
Errno::EACCES (Permission denied @ dir_s_mkdir)Container user cannot write to volume.Run the chown -R 1000:1000 commands in Part 4.
Net::ReadTimeout (Email)Wrong SMTP port or SSL mode.Use Port 587 + SMTP_TLS=false + SMTP_ENABLE_STARTTLS_AUTO=true.
PG::UniqueViolation: duplicate keyUser created but UI showed error.Use Rails Console (User.find_by...) to find and promote the user.

By following this guide, you leverage the power of Linux on your Apple Silicon Mac via OrbStack, resulting in a stable, high-performance Forem community. Happy coding!

You may also like

Subscribe
Notify of
guest

0 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments