最近一段時間密集使用 Docker 佈署各種應用服務,一開始其實沒有特別留意系統資源的使用狀況。直到某一天,突然發現主機前方的系統運作指示燈幾乎沒有熄滅過,心裡只覺得怪怪的。打開系統監控工具一看,才發現事情大條了 —— RAM 幾乎被吃光。
檢查後發現,原來「資源消耗怪獸」是:
Nextcloud + OnlyOffice(Community Server + Document Server + Elasticsearch)+ MySQL
光是完成佈署、系統閒置、甚至還沒做任何壓力測試,記憶體使用量就已經輕鬆突破 10GB。
「好吧,那就買 RAM 來改善把~~~」
一查價格,我的老天鵝啊~~~~~ 今年 2 月才剛組了一台新電腦,同樣品牌、同樣型號。 32GB RAM,當時大約 NT$2,800。現在再查看一次價格 —— 接近3倍旳 NT$9,900。
回想幾個星期前,曾在某個平台(一時想不起來是哪裡)聽到有人提到:
由於 AI 需求動能強勁,記憶體(RAM)價格正在快速上漲,且漲勢仍在持續。
當時心裡還想:「能漲到什麼程度?」
現在才發現自己真的是天真了。
目前先將佈署流程與設定完整記錄下來,才不會以後忘記佈署的細節。接下來打算利用現有的 2 台電腦,練習如何將不同的 service 佈署在不同的 server 上協同運作,分散 RAM 與 I/O 的資源消耗。
Bash Script × 2, dirCREATOR / initSQL
Docker Compose(yml)× 1
.env x1
dirCREATOR
負責根據 docker-compose.yml 中定義的 volume 結構,自動建立所有必要的資料夾與目錄階層。
initSQL
剛開始佈署,在這卡了很久。 Docker Compose 初始啟動時,SQL 初始化存在 bug,如果沒有先將SQL初始化搞定,會一直卡關。
佈署流程
dirCREATOR
↓
initSQL
↓
docker compose up -d
dirCREATOR
#!/usr/bin/env bash
# Base data directory relative to the script location
BASE_DIR="./data"
echo "Creating directory structure for Nextcloud + OnlyOffice ( community + document + elacticsearch ) + MySQL"
# Format: "path:owner_uid:group_gid:permissions"
DIRS=(
"$BASE_DIR/nextcloud_data:33:33:750"
"$BASE_DIR/mysql/conf.d:0:0:755"
"$BASE_DIR/mysql/docker-entrypoint-initdb.d:0:0:755"
"$BASE_DIR/mysql_data:999:999:750"
"$BASE_DIR/community_data:0:0:755"
"$BASE_DIR/community_log:0:0:755"
"$BASE_DIR/community_letsencrypt:0:0:755"
"$BASE_DIR/document_data:0:0:755"
"$BASE_DIR/document_log:0:0:755"
"$BASE_DIR/document_forgotten:0:0:755"
"$BASE_DIR/document_fonts:0:0:755"
"$BASE_DIR/certs:0:0:755"
"$BASE_DIR/es_data:1000:1000:775"
"$BASE_DIR/mail_data:0:0:755"
"$BASE_DIR/mail_certs:0:0:755"
"$BASE_DIR/mail_log:0:0:755"
"$BASE_DIR/controlpanel_data:0:0:755"
"$BASE_DIR/controlpanel_log:0:0:755"
)
if [ "$EUID" -ne 0 ]; then
echo "Please run as root (use sudo)"
exit 1
fi
for entry in "${DIRS[@]}"; do
IFS=':' read -r path uid gid perm <<< "$entry"
echo "Processing: $path"
mkdir -p "$path"
chown "$uid:$gid" "$path"
chmod "$perm" "$path"
done
echo "Directory setup complete."
initSQL
#!/usr/bin/env bash
# --- ONLYOFFICE & NEXTCLOUD MySQL Configuration Creator ---
set -e
# --- 1. Define Paths and Credentials ---
BASE_DIR="./data/mysql"
CONF_DIR="${BASE_DIR}/conf.d"
INIT_DB_DIR="${BASE_DIR}/docker-entrypoint-initdb.d"
CONF_FILE="${CONF_DIR}/onlyoffice.cnf"
INIT_SQL_FILE="${INIT_DB_DIR}/init.sql"
# Credentials
OO_USER="onlyoffice"
OO_PASS="adjust to your pass"
MAIL_USER="onlyoffice_mail"
MAIL_PASS="adjust to your pass"
# Nextcloud Credentials
NC_DB="nextcloud"
NC_USER="onlyoffice"
NC_PASS="adjust to your pass"
# --- 2. Ensure Directories Exist ---
echo "Ensuring configuration directories exist..."
mkdir -p "$CONF_DIR"
mkdir -p "$INIT_DB_DIR"
# --- 3. Write MySQL Configuration ---
echo "Writing MySQL configuration to ${CONF_FILE}..."
cat << 'EOF' > "$CONF_FILE"
[mysqld]
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
max_allowed_packet=256M
innodb_buffer_pool_size=1G
innodb_log_file_size=256M
innodb_flush_log_at_trx_commit=2
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
binlog_format=ROW
EOF
# --- 4. Write Database Initialization Script ---
echo "Writing database user initialization script to ${INIT_SQL_FILE}..."
cat << EOF > "$INIT_SQL_FILE"
-- 1. Create Databases
CREATE DATABASE IF NOT EXISTS onlyoffice CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE IF NOT EXISTS onlyoffice_mailserver CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE IF NOT EXISTS ${NC_DB} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 2. Create Users
CREATE USER IF NOT EXISTS '${OO_USER}'@'%' IDENTIFIED WITH mysql_native_password BY '${OO_PASS}';
CREATE USER IF NOT EXISTS '${MAIL_USER}'@'%' IDENTIFIED WITH mysql_native_password BY '${MAIL_PASS}';
-- 3. Grant Privileges
GRANT ALL PRIVILEGES ON onlyoffice.* TO '${OO_USER}'@'%';
GRANT ALL PRIVILEGES ON onlyoffice_mailserver.* TO '${MAIL_USER}'@'%';
GRANT ALL PRIVILEGES ON ${NC_DB}.* TO '${OO_USER}'@'%';
-- 4. Finalize
FLUSH PRIVILEGES;
-- Set to Taipei time (+08:00)
SET GLOBAL time_zone = '+08:00';
EOF
echo "Initial scripts created successfully."
.env
# -----------------------------
# SYSTEM
# -----------------------------
TZ=Asia/Taipei
MYSQL_ROOT_PASSWORD="adjust to your pass"
# -----------------------------
# DATABASE
# -----------------------------
MYSQL_SERVER_HOST=onlyoffice-mysql-server
MYSQL_SERVER_USER=onlyoffice
MYSQL_SERVER_PASS="adjust to your pass"
MYSQL_SERVER_DB_NAME=onlyoffice
# -----------------------------
# MAIL
# -----------------------------
MAIL_SERVER_HOSTNAME="adjust to your hostname"
MAIL_DB_NAME=onlyoffice_mailserver
MAIL_DB_USER=onlyoffice_mail
MAIL_DB_PASS="adjust to your pass"
# -----------------------------
# SMTP RELAY
# -----------------------------
MAIL_RELAY_HOST="adjust to your hostname"
MAIL_RELAY_PORT=587
MAIL_RELAY_USER="adjust to your email"
MAIL_RELAY_PASSWD="adjust to your pass"
# -----------------------------
# SECURITY (UNIQUE FOR GERARDCHEN INSTANCE)
# -----------------------------
JWT_SECRET="adjust to your secret"
DOCUMENT_SERVER_JWT_HEADER=AuthorizationJwt
ONLYOFFICE_CORE_MACHINEKEY="adjust to your key"
# -----------------------------
# NEXTCLOUD
# -----------------------------
NEXTCLOUD_DB_PASSWORD="adjust to your pass"
# -----------------------------
# DOMAINS
# -----------------------------
CLOUD_DOMAIN="adjust to your url"
OFFICE_DOMAIN="adjust to your url"
DOCUMENT_DOMAIN="adjust to your url"
docker-compose.yml
services:
# --- REDIS ---
redis:
image: redis:alpine
container_name: onlyoffice-redis
restart: always
networks:
- onlyoffice
# --- MYSQL SERVER ---
mysql-server:
container_name: onlyoffice-mysql-server
image: mysql:8.0.29
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- TZ=${TZ}
networks:
- onlyoffice
restart: always
volumes:
- ./data/mysql/conf.d:/etc/mysql/conf.d
- ./data/mysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
- ./data/mysql_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
# --- NEXTCLOUD ---
nextcloud:
image: nextcloud:latest
container_name: nextcloud
restart: always
depends_on:
mysql-server:
condition: service_healthy
redis:
condition: service_started
ports:
- "port:80"
environment:
- MYSQL_HOST=onlyoffice-mysql-server
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=${MYSQL_SERVER_USER}
- MYSQL_PASSWORD=${MYSQL_SERVER_PASS}
- NEXTCLOUD_TRUSTED_DOMAINS=${CLOUD_DOMAIN} domain.local 127.0.0.1
- REDIS_HOST=redis
- OVERWRITEPROTOCOL=https
- OVERWRITEHOST=${CLOUD_DOMAIN}
- TZ=${TZ}
volumes:
- ./data/nextcloud_data:/var/www/html
- /mnt/path to your physical harddisk:/path to your physical harddisk
- /mnt/path to your physical harddisk:/mnt/path to your physical harddisk
- /mnt/path to your physical harddisk:/mnt/path to your physical harddisk
- /mnt/path to your physical harddisk:/mnt/path to your physical harddisk:
- onlyoffice
# --- DOCUMENT SERVER ---
onlyoffice-document-server:
container_name: onlyoffice-document-server
image: onlyoffice/documentserver:8.1
restart: always
environment:
- JWT_ENABLED=true
- JWT_SECRET=${JWT_SECRET}
- JWT_HEADER=${DOCUMENT_SERVER_JWT_HEADER}
- TZ=${TZ}
- REDIS_SERVER_HOST=redis
networks:
- onlyoffice
ports:
- 'port:80'
volumes:
- ./data/document_data:/var/www/onlyoffice/Data
- ./data/document_log:/var/log/office
- ./data/document_fonts:/usr/share/fonts/truetype/custom
- ./data/document_forgotten:/var/lib/onlyoffice/documentserver/App_Data/cache/files/forgotten
# --- ONLYOFFICE COMMUNITY SERVER ---
onlyoffice-community-server:
container_name: onlyoffice-community-server
image: onlyoffice/communityserver:12.6.0.1900
depends_on:
mysql-server:
condition: service_healthy
onlyoffice-document-server:
condition: service_started
redis:
condition: service_started
environment:
- ONLYOFFICE_CORE_MACHINEKEY=${ONLYOFFICE_CORE_MACHINEKEY}
- TZ=${TZ}
- REDIS_SERVER_HOST=redis
- DOCUMENT_SERVER_PORT_80_TCP_ADDR=onlyoffice-document-server
- DOCUMENT_SERVER_PORT_80_TCP_PROTO=http
- DOCUMENT_SERVER_URL_PUBLIC=https://${DOCUMENT_DOMAIN}/
- DOCUMENT_SERVER_URL_INTERNAL=http://onlyoffice-document-server/
- DOCUMENT_SERVER_JWT_ENABLED=true
- DOCUMENT_SERVER_JWT_SECRET=${JWT_SECRET}
- DOCUMENT_SERVER_JWT_HEADER=${DOCUMENT_SERVER_JWT_HEADER}
- MYSQL_SERVER_HOST=onlyoffice-mysql-server
- MYSQL_SERVER_DB_NAME=${MYSQL_SERVER_DB_NAME}
- MYSQL_SERVER_USER=${MYSQL_SERVER_USER}
- MYSQL_SERVER_PASS=${MYSQL_SERVER_PASS}
networks:
- onlyoffice
ports:
- 'port:80'
privileged: true
cgroup: host
restart: always
volumes:
- ./data/community_data:/var/www/onlyoffice/Data
- ./data/community_log:/var/log/onlyoffice
- ./data/community_ds_data:/var/www/onlyoffice/DocumentServerData
- /sys/fs/cgroup:/sys/fs/cgroup:rw
- ./data/certs:/var/www/onlyoffice/Data/certs
# --- ELASTICSEARCH ---
onlyoffice-elasticsearch:
image: onlyoffice/elasticsearch:7.16.3
container_name: onlyoffice-elasticsearch
restart: always
environment:
- discovery.type=single-node
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms1g -Xmx1g"
networks:
- onlyoffice
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- ./data/es_data:/usr/share/elasticsearch/data
# --- MAIL SERVER ---
onlyoffice-mail-server:
container_name: onlyoffice-mail-server
image: onlyoffice/mailserver:1.6.75
depends_on:
mysql-server:
condition: service_healthy
hostname: ${MAIL_SERVER_HOSTNAME}
environment:
- MYSQL_SERVER=onlyoffice-mysql-server
- MYSQL_SERVER_PORT=3306
- MYSQL_ROOT_USER=${MAIL_DB_USER}
- MYSQL_ROOT_PASSWD=${MAIL_DB_PASS}
- MYSQL_SERVER_DB_NAME=${MAIL_DB_NAME}
- TZ=${TZ}
- MAIL_RELAY_HOST=${MAIL_RELAY_HOST}
- MAIL_RELAY_PORT=${MAIL_RELAY_PORT}
- MAIL_RELAY_USER=${MAIL_RELAY_USER}
- MAIL_RELAY_PASSWD=${MAIL_RELAY_PASSWD}
networks:
- onlyoffice
restart: always
privileged: true
volumes:
- ./data/mail_data:/var/vmail
- ./data/mail_certs:/etc/pki/tls/mailserver
- ./data/mail_log:/var/log
# --- CONTROL PANEL ---
onlyoffice-control-panel:
container_name: onlyoffice-control-panel
image: onlyoffice/controlpanel:3.5.2.530
depends_on:
- onlyoffice-document-server
- onlyoffice-mail-server
- onlyoffice-community-server
environment:
- ONLYOFFICE_CORE_MACHINEKEY=${ONLYOFFICE_CORE_MACHINEKEY}
- TZ=${TZ}
restart: always
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./data/controlpanel_data:/var/www/onlyoffice/Data
- ./data/controlpanel_log:/var/log/onlyoffice
networks:
- onlyoffice
networks:
onlyoffice:
driver: 'bridge'