💲

800円/月のおひとり様Misskey環境を構築する

に公開

まえがき

この記事は、できるだけランニングコストを抑えてMisskeyの個人インスタンスの運用を試み、その環境構築過程を個人的な備忘録としてまとめたものです。
なお、Misskeyのアップデートや各クラウドサービスの仕様変更によって将来的にこの手順が使用できなくなる可能性があります。(記.2025/11/02)

要約

  • Misskeyはおひとり様ないし少人数での利用を想定
  • VPSとしてKAGOYA VPSの2コア2GBプランを使用
  • 自分のドメインは既に持っており、サブドメインを割り当て
  • オブジェクトストレージとしてCloudflare R2を使用

環境構築

VPSサーバーの準備

KAGOYA VPSというサービスを使用します。
https://www.kagoya.jp/vps/

大手クラウドほどの信頼性は期待できませんが、個人用途なら業務レベルの可用性は不要ですし、国内サービスという点では一定の安心感があります。2コア2GBで700円台/月(執筆時点)と手頃だったので、試してみることにしました。

OSはUbuntu 24.04 LTS、プランは2コア2GBでリソースを作成します。

VPSに接続したらパッケージを更新、再起動しておきます。

sudo apt update
sudo apt upgrade
sudo reboot

Dockerのインストール

公式レポジトリの手順に従ってインストールします。
https://docs.docker.com/engine/install/ubuntu/#set-up-the-repository

sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo docker run hello-world

Misskeyのセットアップ

公式サイトの手順に従ってセットアップを行います。
https://misskey-hub.net/ja/docs/for-admin/install/guides/docker/

git clone -b master https://github.com/misskey-dev/misskey.git
cd misskey
git checkout master

cp .config/docker_example.yml .config/default.yml
cp .config/docker_example.env .config/docker.env
cp ./compose_example.yml ./compose.yml

.config/default.yml, .config/docker.env, ./compose.ymlを書き換えます。

nano .config/default.yml

編集内容

  • url: Misskeyインスタンスで使用する自分のURL(一度決めたら変更不可)
  • user: DBで使用するユーザー名(おそらく何でも良い)
  • pass: DBで使用するパスワード(おそらく何でも良い)
nano .config/docker.env

編集内容

  • user: DBで使用するユーザー名(↑と同じもの)
  • pass: DBで使用するパスワード(↑と同じもの)
nano compose.yml

MisskeyはDockerイメージが公開されているので、それを利用するように変更します。(2コア2GBだとメモリが足りず手元でフロントエンドをビルドできない)

compose.yml
services:
  web:
#   build: .  # ここをコメントアウト
    image: misskey/misskey:latest  # ここを追加
    restart: always
...(略)

マイグレーションなどを行い、curlでHTMLが返ってくることを確認します。

sudo docker compose run --rm web pnpm run init
sudo docker compose up -d
curl localhost:3000

DNSとNginxのセットアップ

CloudflareでAPIトークンを発行しておきます。
https://dash.cloudflare.com/profile/api-tokens

/etc/cloudflare/cloudflare.iniを作成します。

sudo apt install -y nginx certbot python3-certbot-dns-cloudflare
sudo mkdir /etc/cloudflare
sudo nano /etc/cloudflare/cloudflare.ini
sudo chmod 600 /etc/cloudflare/cloudflare.ini
/etc/cloudflare/cloudflare.ini
dns_cloudflare_api_token = {YOUR_TOKEN}

Misskeyのdefault.ymlに設定したドメインで証明書を発行します。

sudo certbot certonly --dns-cloudflare --dns-cloudflare-credentials /etc/cloudflare/cloudflare.ini --dns-cloudflare-propagation-seconds 60 --server https://acme-v02.api.letsencrypt.org/directory -d {YOUR_DOMAIN} -d *.{YOUR_DOMAIN}

Misskeyのドキュメントに従ってNginxを設定します。
https://misskey-hub.net/ja/docs/for-admin/install/resources/nginx/

sudo nano /etc/nginx/sites-available/misskey
長いので折り畳み
/etc/nginx/sites-available/misskey
# For WebSocket
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

proxy_cache_path /tmp/nginx_cache levels=1:2 keys_zone=cache1:16m max_size=1g inactive=720m use_temp_path=off;

server {
    listen 80;
    listen [::]:80;
    server_name {YOUR_DOMAIN};

    # For SSL domain validation
    root /var/www/html;
    location /.well-known/acme-challenge/ { allow all; }
    location /.well-known/pki-validation/ { allow all; }
    location / { return 301 https://$server_name$request_uri; }
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    #http2 on;
    server_name {YOUR_DOMAIN};

    ssl_session_timeout 1d;
    ssl_session_cache shared:ssl_session_cache:10m;
    ssl_session_tickets off;

    # To use Let's Encrypt certificate
    ssl_certificate     /etc/letsencrypt/live/{YOUR_DOMAIN}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/{YOUR_DOMAIN}/privkey.pem;

    # To use Debian/Ubuntu's self-signed certificate (For testing or before issuing a certificate)
    #ssl_certificate     /etc/ssl/certs/ssl-cert-snakeoil.pem;
    #ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;

    # SSL protocol settings
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_stapling on;
    ssl_stapling_verify on;

    # Change to your upload limit
    client_max_body_size 80m;

    # Proxy to Node
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_http_version 1.1;
        proxy_redirect off;

        # If it's behind another reverse proxy or CDN, remove the following.
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;

        # For WebSocket
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        # Cache settings
        proxy_cache cache1;
        proxy_cache_lock on;
        proxy_cache_use_stale updating;
        proxy_force_ranges on;
        add_header X-Cache $upstream_cache_status;
    }
}
sudo ln -s /etc/nginx/sites-available/misskey /etc/nginx/sites-enabled/misskey
sudo nginx -t
sudo systemctl restart nginx

Cloudflare側でDNSの設定をすればブラウザからMisskeyにアクセスできるようになるはずです。

オブジェクトストレージの設定

Cloudflare R2のバケットとAPIトークンを作成します。

バケットにはカスタムドメインを設定しておきます。(以降、ドメインは適宜読み替えてください)

Misskey側のコントロールパネルから以下のように設定します。画像には写っていませんが、APIトークン作成時に表示されるAccess keyとSecret keyも必要です。

これで試しに画像をアップロードし、ちゃんと表示できていれば設定は完了です。

あとがき

この構成のランニングコストはVPSが700円台/月、Cloudflare R2は10GBまで無料なのでおひとり様インスタンスなら1ドル未満に収まると思います。
自分のドメインでMisskeyをこのコストで動かせる(ドメインは既に持っている前提としてノーカン)なら、結構アリなんじゃないでしょうか。まだこのインスタンスは立ち上げたばかりなので、実際に数か月運用してみて後日追記しようと思います。それでは。

Discussion