Đợt Back to School của Apple vừa rồi mình thấy con Mac Mini M4 giá tốt quá nên mua trải nghiệm, dùng chán rồi nên giờ mang ra làm cái homelab để cài mấy ứng dụng nhỏ nhỏ. Tiện cài thử Forem làm cái cộng đồng chia sẻ về AI và Automation, nên làm lại bài hướng dẫn cho anh em nào cũng có cùng nhu cầu.
Tuy nhiên, việc triển khai Forem (mã nguồn của DEV.to) trên macOS sử dụng chip Apple Silicon (M1/M2/M3/M4) thực sự là một “kiếp nạn”. Nếu bạn cố chạy Docker trực tiếp trên Mac, bạn sẽ đụng phải hàng tá vấn đề: từ lỗi biên dịch thư viện nokogiri, xung đột kiến trúc amd64 vs arm64, cho đến cơn ác mộng về quyền ghi file (permission denied).
Sau nhiều lần đập đi xây lại, mình đã tìm ra giải pháp tối ưu nhất cho anh em dùng Mac: Chạy một máy ảo Linux (Ubuntu) nhẹ thông qua OrbStack, và triển khai Forem bên trong đó. Bài viết này mình sẽ hướng dẫn chi tiết từng bước, bao gồm cả cách “vá” những lỗi mà tài liệu chính thức của Forem chưa cập nhật kịp.
Nào, pha cốc cà phê và bắt đầu nhé!

Chuẩn bị môi trường (OrbStack & Ubuntu VM)
Thay vì dùng Docker Desktop, chúng ta dùng OrbStack vì nó nhẹ, nhanh và hỗ trợ quản lý máy ảo Linux cực tốt. Nếu chưa cài OrbStack thì bạn xem bài hướng dẫn này: OrbStack – Lựa chọn thay thế lý tưởng cho Docker Desktop trên macOS
1. Tạo máy ảo Ubuntu
Mở Terminal trên Mac và chạy lệnh sau để tạo một máy ảo Ubuntu 24.04 (Noble). Chúng ta sẽ đặt tên cho máy ảo này là forem-server:
orb create ubuntu:noble forem-server2. Truy cập vào máy ảo
Từ bước này trở đi, mọi câu lệnh chúng ta sẽ gõ bên trong máy ảo này, không phải trên Terminal gốc của Mac nhé. Để chui vào máy ảo, bạn gõ:
orb -m forem-server3. Cài đặt Docker & Docker Compose bên trong VM
Mặc dù OrbStack đã tích hợp Docker, nhưng để có một môi trường khép kín chuẩn Production (giống như bạn đang thuê VPS thật), mình khuyên nên cài đặt Docker Engine riêng cho Ubuntu bên trong máy ảo này.
Chạy lần lượt các lệnh sau để cài đặt:
# Cập nhật hệ thống
sudo apt-get update && sudo apt-get upgrade -y
# Cài đặt Docker
sudo apt-get install -y docker.io docker-compose-v2
# Thêm user hiện tại vào nhóm docker (để không cần gõ sudo)
sudo usermod -aG docker $USER
newgrp dockerTải mã nguồn & cấu hình
1. Clone Code (Lưu ý quan trọng)
Tuyệt đối không clone code ở ngoài Mac rồi mount vào. Hãy clone trực tiếp vào ổ cứng của máy ảo để đạt tốc độ tối đa và tránh lỗi Permission denied.
cd ~
git clone https://github.com/forem/forem.git
cd forem2. “Vá” lỗi Containerfile
Image gốc của Forem hiện tại đang dính 2 lỗi rất nặng khiến bạn không thể build được:
- Repository của Node.js (NodeSource) bị cũ, gây lỗi 404 khi
apt update. - Thiếu đường dẫn Bash chuẩn, gây lỗi
exec: "/usr/bin/bash": not found.
Thay vì mở file ra sửa thủ công dễ nhầm, bạn chạy 2 lệnh sed này để tự động vá lỗi:
# Fix 1: Xóa repo NodeSource cũ gây lỗi build 404
sed -i 's/apt update/rm -f \/etc\/apt\/sources.list.d\/nodesource.list \&\& apt update/g' Containerfile
# Fix 2: Tạo symlink cho bash (Sửa lỗi: exec: "/usr/bin/bash": not found)
sed -i '/apt-get install -yq --no-install-recommends \\/a \ ln -s /bin/bash /usr/bin/bash && \\' Containerfile3. Cấu hình file .env
Tạo file biến môi trường .env:
nano .envDưới đây là nội dung file cấu hình đã được tối ưu. Bạn hãy copy và dán vào, nhớ thay đổi các thông tin ten-mien-cua-ban cho phù hợp:
# APP IDENTITY
APP_DOMAIN="ten-mien-cua-ban.com"
APP_PROTOCOL="https://"
COMMUNITY_NAME="My Community"
DEFAULT_EMAIL="[email protected]" # Phải trùng với SMTP User bên dưới
TZ=Asia/Ho_Chi_Minh
# SECRETS (Bạn có thể tạo chuỗi tại đây: https://tools.vnrom.me/token-generator)
FOREM_OWNER_SECRET="chuoi_bi_mat_dai_ngoang_1"
SECRET_KEY_BASE="chuoi_bi_mat_dai_ngoang_2"
# DATABASE & REDIS (Trỏ về service name, KHÔNG dùng localhost)
DATABASE_URL="postgresql://forem:forem@postgres:5432/forem_production"
DATABASE_NAME="forem_production"
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 (Ví dụ dùng Larksuite/Gmail) ---
SMTP_ADDRESS="smtp.larksuite.com"
SMTP_PORT="465"
SMTP_DOMAIN="ten-mien-cua-ban.com"
SMTP_USER_NAME="[email protected]"
SMTP_PASSWORD="mat_khau_ung_dung"
SMTP_AUTHENTICATION="login"
SMTP_TLS=true
SMTP_ENABLE_STARTTLS_AUTO=false
SMTP_OPENSSL_VERIFY_MODE="peer"
# --- SECURITY ---
FORCE_SSL_IN_RAILS="true"
4. Tạo file docker-compose.yml
File mặc định của Forem khá rối rắm. Chúng ta sẽ xóa file cũ và tạo một file mới gọn gàng hơn, tối ưu cho chạy production.
nano docker-compose.ymlCopy và paste nội dung sau (Lưu ý: dòng target: production là chìa khóa để fix lỗi thiếu Gemfile khi build):
services:
postgres:
image: postgres:13
container_name: forem_postgres
restart: always
env_file:
- .env
environment:
POSTGRES_USER: forem
POSTGRES_PASSWORD: forem
POSTGRES_DB: forem_production
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "forem"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:6.2-alpine
container_name: forem_redis
restart: always
env_file:
- .env
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
web:
build:
context: .
dockerfile: Containerfile
target: production # BẮT BUỘC CÓ để copy code vào image
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:
- 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 và chạy
Đây là lúc chúng ta kiểm chứng thành quả. Hãy chạy lần lượt các lệnh sau thật cẩn thận:
1. Build Image:
Quá trình này sẽ hơi lâu một chút tùy vào mạng và cấu hình máy ảo.
docker compose build2. Khởi tạo Database:
Chúng ta dùng bundle exec thay vì bin/rails để đảm bảo lệnh được chạy trong đúng ngữ cảnh của Ruby gems.
docker compose run --rm web bundle exec rails db:setup3. Fix quyền thư mục Upload (Bắt buộc):
Nếu bỏ qua bước này, Forem sẽ chạy được nhưng bạn không thể upload avatar hay ảnh bài viết (Lỗi Errno::EACCES). Docker thường gán quyền root cho volume mới tạo, trong khi user chạy app lại là forem (UID 1000).
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/public4. Biên dịch giao diện:
docker compose run --rm web bundle exec rails assets:precompile5. Khởi động hệ thống:
docker compose up -dTạo tài khoản Admin
Rất nhiều bạn sau khi cài xong, hí hửng vào web đăng ký tài khoản thì bị lỗi. Nguyên nhân thường là do SMTP chưa thông, email xác nhận không gửi đi được.
Cách “chắc cú” nhất là tạo tài khoản Super Admin trực tiếp qua dòng lệnh console:
- Truy cập Console:
docker compose exec web bundle exec rails c- Gõ các lệnh sau để tạo Super Admin:
# Tạo user
u = User.new(
name: "Super Admin",
username: "admin",
email: "[email protected]",
password: "MatKhauSieuManh123",
password_confirmation: "MatKhauSieuManh123"
)
u.save!
# Kích hoạt & Cấp quyền
u.confirm
u.add_role(:admin)
u.add_role(:super_admin)
u.save!
exitBây giờ bạn có thể đăng nhập ngay tại https://ten-mien-cua-ban.com (hoặc localhost:3000) mà không cần chờ email.
Lưu ý: Sau khi đăng nhập thành công, hãy vào Dashboard Admin, cấu hình lại SMTP cho chuẩn rồi restart lại container bằng 2 lệnh:
docker compose down
docker compose up -dTổng hợp các lỗi thường gặp & cách fix
Trong quá trình cài đặt, nếu “nhân phẩm” chưa tốt và gặp lỗi, bạn hãy tra cứu bảng thần chú này:
- Lỗi:
nodesource ... does not have a Release file- Nguyên nhân: Repo Node cũ trong Image bị chết.
- Khắc phục: Dùng lệnh
sed(Fix 1) ở mục Tải mã nguồn để xóa file list lỗi.
- Lỗi:
exec: "/usr/bin/bash": not found- Nguyên nhân: Script gọi sai đường dẫn Bash (do khác biệt OS).
- Khắc phục: Dùng lệnh
sed(Fix 2) để tạo symlinkln -s /bin/bash /usr/bin/bash.
- Lỗi:
Could not locate Gemfile- Nguyên nhân: Docker build không tìm thấy code.
- Khắc phục: Thêm dòng
target: productionvàodocker-compose.yml.
- Lỗi:
ActiveRecord::ConnectionNotEstablished ... 127.0.0.1- Nguyên nhân: Rails tìm DB ở localhost của container thay vì service
postgres. - Khắc phục: Sửa
.env, đảm bảoDATABASE_URL=postgresql://...@postgres:5432....
- Nguyên nhân: Rails tìm DB ở localhost của container thay vì service
- Lỗi:
Errno::EACCES (Permission denied @ dir_s_mkdir)- Nguyên nhân: Container không có quyền ghi vào thư mục uploads.
- Khắc phục: Chạy bộ lệnh
chownvàchmodở bước Fix quyền thư mục Upload.
- Lỗi:
PG::UniqueViolation: duplicate key- Nguyên nhân: Tài khoản đã tạo rồi nhưng UI báo lỗi ảo.
- Khắc phục: Vào Rails Console, tìm user (
User.find_by_email), rồi chạy lệnhconfirm+add_role.
Quy trình update Forem
Vận hành một hệ thống Production không chỉ là cài đặt xong rồi để đó. Bạn cần biết cách cập nhật code mới từ Forem để vá lỗi bảo mật và quan trọng hơn là sao lưu dữ liệu đề phòng sự cố.
Vì đang chạy trên máy ảo Ubuntu (OrbStack) với Docker Compose, quy trình này sẽ thực hiện hoàn toàn trong Terminal của Ubuntu
Mã nguồn Forem thay đổi liên tục. Để cập nhật phiên bản mới nhất từ Github mà không làm hỏng hệ thống hiện tại, hãy làm theo các bước sau:
Bước 1: Chuẩn bị
Truy cập vào thư mục code:
cd ~/foremBước 2: Tải code mới nhất về
LƯU Ý:
Vì chúng ta đã sửa file Containerfile bằng lệnh sed trước đó, khi git pull có thể sẽ báo lỗi xung đột hoặc ghi đè. Tốt nhất là reset lại file này, kéo code mới, rồi vá lỗi lại.
# Hủy các thay đổi local để nhận code mới sạch sẽ
git checkout Containerfile
# Kéo code mới nhất từ nhánh main
git pull origin main
Bước 3: Vá lại lỗi Containerfile (Bắt buộc)
Code mới tải về vẫn sẽ dính lỗi cũ (NodeSource & Bash path), bạn phải chạy lại 2 lệnh thần thánh này:
# Fix lỗi NodeSource & Bash Path
sed -i 's/apt update/rm -f \/etc\/apt\/sources.list.d\/nodesource.list \&\& apt update/g' Containerfile
sed -i '/apt-get install -yq --no-install-recommends \\/a \ ln -s /bin/bash /usr/bin/bash && \\' ContainerfileBước 4: Build và cập nhật Database
Quá trình này sẽ đóng gói code mới vào Image và cập nhật cấu trúc bảng dữ liệu (nếu có thay đổi).
# 1. Build lại Image Production (Mất vài phút)
docker compose build
# 2. Chạy Migration (Cập nhật cấu trúc DB)
# Lưu ý: KHÔNG chạy db:setup hay db:reset ở bước này (sẽ mất dữ liệu)
docker compose run --rm web bundle exec rails db:migrate
# 3. Biên dịch lại giao diện (Assets)
docker compose run --rm web bundle exec rails assets:precompileBước 5: Khởi động lại
# Khởi động lại container với image mới
docker compose up -d
# Xóa bớt image cũ cho nhẹ máy
docker image prune -fQuy trình sao lưu (Backup)
Dữ liệu quan trọng nhất của Forem nằm ở 3 nơi:
- Database (PostgreSQL): Chứa bài viết, user, comment…
- Uploads: Chứa ảnh avatar, ảnh bài viết (nằm trong thư mục
public/uploads). - File cấu hình: File
.env.
Dưới đây là quy trình thiết lập backup toàn bộ dữ liệu Forem (Database + Ảnh Uploads + Config) lên Google Drive thông qua công cụ Rclone, chạy tự động hàng ngày.
Bạn thực hiện toàn bộ các bước này trong Terminal của máy ảo Ubuntu.
Cài đặt và cấu hình Rclone
Rclone là công cụ dòng lệnh mạnh mẽ để đồng bộ dữ liệu lên Cloud.
1. Cài đặt Rclone
sudo -v ; curl https://rclone.org/install.sh | sudo bashNếu nó báo lỗi “None of the supported tools for extracting zip archives (unzip 7z busybox) were found. Please install one of them and try again.” thì bạn cần cài thêm zip cho nó: sudo apt install zip
2. Cấu hình kết nối Google Drive (Headless Auth)
Vì máy ảo Ubuntu không có trình duyệt để bạn đăng nhập Google, chúng ta cần làm bước này hơi đặc biệt một chút (kết hợp với máy Mac của bạn).
Trên máy ảo Ubuntu:
- Gõ lệnh:
rclone config - Chọn
n(New remote). - Đặt tên:
gdrive(hoặc tên gì bạn thích). - Chọn Storage: Tìm số tương ứng với Google Drive (thường là số 22 hoặc quanh đó) và nhập vào.
client_id: Để trống (Enter).client_secret: Để trống (Enter).scope: Chọn1(Full access).service_account_file: Để trống (Enter).Edit advanced config?: Chọnn.- QUAN TRỌNG:
Use auto config?-> Chọnn(No).
Rclone sẽ hiện ra một dòng lệnh dài loằng ngoằng dạng rclone authorize "drive" "...". Hãy copy dòng lệnh đó.
Trên máy Mac (Terminal của Mac, không phải VM):
- Mở Terminal mới trên Mac.
- Dán dòng lệnh vừa copy vào và Enter.
- Trình duyệt sẽ bật lên, bạn đăng nhập Google và cấp quyền cho Rclone.
- Sau khi xong, Terminal Mac sẽ hiện ra một mã token (dạng
{"access_token":...}). Copy toàn bộ mã token này.
Quay lại máy ảo Ubuntu:
- Dán mã token vào chỗ
config_token>. - Chọn
n(Keep this “team drive” configuration). - Chọn
y(Yes this is ok). - Chọn
q(Quit).
Lúc này bạn đã kết nối thành công Ubuntu với Google Drive.
Viết script backup tự động
Chúng ta sẽ tạo một script để làm các việc: Dump database -> Nén thư mục uploads -> Copy lên GDrive -> Xóa file tạm.
1. Tạo file script
mkdir -p ~/scripts
nano ~/scripts/backup_forem.sh2. Dán nội dung sau vào file (Sửa lại các đường dẫn cho đúng với user của bạn)
#!/bin/bash
# --- CẤU HÌNH ---
# 1. Tên Rclone Remote bạn vừa tạo
RCLONE_REMOTE="gdrive"
# 2. Thư mục trên Google Drive để lưu backup
GDRIVE_FOLDER="Forem_Backups"
# 3. Thư mục chứa code Forem trên máy ảo
FOREM_DIR="/home/vnrom/forem"
# 4. Thư mục tạm để chứa file backup trước khi up
BACKUP_DIR="/home/vnrom/backups_temp"
# 5. Định dạng ngày tháng
DATE=$(date +%Y-%m-%d_%H-%M-%S)
TARGET_DIR="$BACKUP_DIR/$DATE"
# --- BẮT ĐẦU ---
echo "[$(date)] Bắt đầu backup Forem..."
# Tạo thư mục tạm
mkdir -p "$TARGET_DIR"
# 1. Backup Database (Nén trực tiếp để tiết kiệm dung lượng)
echo "-> Dumping Database..."
cd $FOREM_DIR
# Lưu ý: Dùng -T để tắt TTY (quan trọng khi chạy cron)
docker compose exec -T postgres pg_dump -U forem forem_production | gzip > "$TARGET_DIR/database.sql.gz"
# 2. Backup Uploads (Ảnh, Avatar...)
echo "-> Archiving Uploads..."
# Nén thư mục public/uploads từ trong container web ra ngoài
docker compose exec -T web tar -czf - /opt/apps/forem/public/uploads > "$TARGET_DIR/uploads.tar.gz"
# 3. Backup File Config (.env)
echo "-> Copying Config..."
cp "$FOREM_DIR/.env" "$TARGET_DIR/.env_backup"
# 4. Upload lên Google Drive
echo "-> Uploading to Google Drive..."
# Copy toàn bộ folder ngày hôm nay lên
rclone copy "$TARGET_DIR" "$RCLONE_REMOTE:$GDRIVE_FOLDER/$DATE" --transfers=4
# 5. Dọn dẹp file tạm trên máy ảo (Để không bị đầy ổ cứng)
echo "-> Cleaning up local files..."
rm -rf "$TARGET_DIR"
# 6. (Tùy chọn) Xóa backup cũ trên Drive quá 30 ngày
echo "-> Deleting old backups on Drive (>30 days)..."
rclone delete "$RCLONE_REMOTE:$GDRIVE_FOLDER" --min-age 30d --rmdirs
echo "[$(date)] ✅ Backup hoàn tất!"3. Cấp quyền thực thi cho script
chmod +x ~/scripts/backup_forem.shChạy thử và cronjob
1. Chạy thử bằng tay
Hãy chạy lệnh sau để xem script có hoạt động không:
~/scripts/backup_forem.shNếu nó chạy một mạch, báo “✅ Backup hoàn tất!” và bạn thấy folder mới xuất hiện trên Google Drive của mình, thì mọi thứ đã chuẩn.
2. Cài đặt tự động chạy hàng ngày (Cronjob)
Chúng ta sẽ cài đặt để nó tự chạy vào 3:00 sáng mỗi ngày.
Gõ lệnh:
crontab -eThêm dòng sau vào cuối file:
~/scripts/backup_forem.sh# Chạy backup lúc 3h sáng hàng ngày, ghi log ra file để debug nếu lỗi
0 3 * * * /home/vnrom/scripts/backup_forem.sh >> /home/vnrom/scripts/backup.log 2>&1(Nhớ thay /home/vnrom bằng đường dẫn user thực tế của bạn nếu khác).
Bây giờ hệ thống Forem của bạn đã có cơ chế bảo vệ kép:
- Dữ liệu nằm trên máy ảo Ubuntu (ổ định, không lỗi permission).
- Backup tự động lên Google Drive mỗi ngày.
- Tự động xóa bản cũ sau 30 ngày để không bị full Drive.
Bạn có thể yên tâm phát triển cộng đồng rồi!
Quy trình khôi phục (Restore)
Chỉ thực hiện khi bạn chuyển server, hoặc lỡ tay xóa nhầm dữ liệu. Cảnh báo: Dữ liệu hiện tại sẽ bị ghi đè.
Giả sử bạn đã có 2 file backup: forem_db.sql và uploads.tar.gz.
Bước 1: Dừng hệ thống (Tránh xung đột)
docker compose stop web sidekiqBước 2: Khôi phục Database
Lệnh này sẽ xóa DB hiện tại và nạp lại từ file backup.
# 1. Xóa database cũ và tạo lại database trắng
docker compose exec postgres dropdb -U forem forem_production
docker compose exec postgres createdb -U forem forem_production
# 2. Nạp dữ liệu từ file SQL
# (Lưu ý dấu < để truyền file vào)
cat ~/backups/forem_db.sql | docker compose exec -T postgres psql -U forem forem_productionBước 3: Khôi phục File Uploads
# 1. Copy file nén vào trong container
docker cp ~/backups/uploads.tar.gz forem_web:/tmp/uploads.tar.gz
# 2. Giải nén vào đúng vị trí
docker compose exec web tar -xzf /tmp/uploads.tar.gz -C /opt/apps/forem/public/uploads/
# 3. QUAN TRỌNG: Fix lại quyền (Permission) sau khi restore
# Nếu không làm bước này, ảnh sẽ không hiện hoặc không up mới được
docker compose exec -u root web chown -R 1000:1000 /opt/apps/forem/public/uploads
docker compose exec -u root web chmod -R 777 /opt/apps/forem/public/uploadsBước 4: Khởi động lại
docker compose startTóm tắt các lệnh hay dùng hàng ngày
| Tác vụ | Lệnh |
| Khởi động server | docker compose up -d |
| Dừng server | docker compose stop |
| Xem logs (Web) | docker compose logs -f web |
| Xem logs (Email/Job) | docker compose logs -f sidekiq |
| Vào Console Rails | docker compose exec web bundle exec rails c |
| Check kết nối mạng | docker compose exec web curl -I https://google.com |
Vậy là bạn đã có trong tay trọn bộ bí kíp để vận hành Forem an toàn và bền vững!
Hy vọng hướng dẫn này giúp bạn triển khai thành công Forem trên máy Mac của bạn.








