「今話題のローコードツールを触ってみたいけど、意外と月額が高いのよね…」とお悩みの方におすすめするのがおうちn8n。

だいたいこんな構成になると思います。

なぜ自宅サーバーでn8nを?

まず前提としてSaaS版(Webサービスとしてn8nがホストする環境を利用)のn8nはエンタープライズ版でなければマトモに動かせません。
参考: https://n8n.io/pricing/

制約上50ユーロのPro版ですら「15ワークフローを1ヶ月10000回実行」の制約があり、そもそも気軽に試すのもちょっと辛い。
更に加えるとちょっと手の込んだ、ローコード的なJavascriptの実行に関して任意のnpmで公開されているライブラリを利用できないなど、かゆい所に手が届かない場合も多い。
n8nを自前でビルドする場合「Dockerfileをビルドするタイミングでnode_modulesを追加してCodeブロックで何でも出来る」事ができるので、使い倒すにはセルフホスト版を使いたいところ。
ただ、インフラの構成上、外部DB(RDS等)を付けたり、4GB以上のインスタンス、となるとカジュアルに使うには料金が高くなりがち。自宅サーバーで組んだらカジュアルにn8nを使い倒せますよ、という話。

ハードウェア

n8n自体はNode.jsのランタイムだけなので2GBメモリ+SQLiteで十分動く構成ですが、動かすタスクで大きめのバッチ処理をやる余地を考えると、可能なら8GBのPCが手元にあると良いです。
AmazonではIntel N100 + 8GB +256GB SSDのミニPC(-> https://amzn.to/3OVvwyF )が2万円未満で売っているので、初期投資はこれだけでOKです。ラズパイより安い。

ルーターとサーバーのセットアップ

Ubuntu 22.04 LTSのサーバー版が丁度良い。
基本的にはSSHでログインできるようにしつつルーター側ではDHCPで割り振られるサーバーのローカルIPアドレスを固定して、80番ポートがサーバーに届くようにすればOK。SSHのポートを開くのはあんまオススメしない。
後は docker-compose コマンドを打てるようにしておいて、公式リポジトリをCloneしてdocker-composeのフォルダに行って、 docker-compose up すれば終わりです。

ただし、色々いじるには不足している所もあるので、以下のようにカスタマイズを行います。

https://github.com/ten-mado/n8n-docker-nginx

主に変更したのは以下の点です。

  • postgresのデータをvolumeでマウントする(永続化&バックアップ)
  • n8nアプリのコンテナをDockerfileでカスタムビルドして、バージョン指定したりnode_modulesを任意に追加できるようにする
  • Traefikを使ってLets’EncryptでSSL対応+HSTS
  • 自作のn8nノードを試せるように「n8n-nodes-starter」ディレクトリを配置してコンテナ内で読ませる
    • 公式リポジトリ(https://github.com/n8n-io/n8n-nodes-starter)をCloneしてビルドしてこのディレクトリに配置すれば自作のノードが動きます(n8nの再起動は必要)

DNSは今回はRoute53を使っていますが、DDNSサービスに慣れているならそれらと統合して好みのDNSサービスを使っても良いですし、お好きにどうぞといった感じです。安く済ませるならAPIでDNSを更新できるValue-Domainとかかな…
重要なCredentialsを管理しない前提ならCDNも要らないと思うのですが、漏れて困るデータが存在する場合、最低限そのサーバーは80(443)番ポートだけを開放し、なおかつゼロデイ脆弱性に備えて多段の制御(Basic認証するとか、特定のヘッダのみ許可するとか)を入れるべきです。TraefikではBasic認証をサクっと入れられるのですが、WebhookのエンドポイントだけはBasic認証から除外したいものの方法がわからず、ちょっとセキュリティ対策は要検討です…(ガチ運用ならCDNを前段に噛ませて色々フィルタリングすれば良いと思います)

const { Route53Client, ListResourceRecordSetsCommand, ChangeResourceRecordSetsCommand } = require("@aws-sdk/client-route-53");
const hostedZoneId = 'Z0XXXXXXXXXXXXXXXXXXX';
const recordName = 'xxx.xxx.xxx.';
const actualIpAddress = $input.first().json.data.replaceAll(/[^0-9\.]/g, '');

const command = new ListResourceRecordSetsCommand({
  HostedZoneId: hostedZoneId,
  StartRecordName: recordName,
  StartRecordType: 'A',
  MaxItems: 1
});
const client = new Route53Client({ region: "ap-northeast-1" });
const data = await client.send(command);

const awsRecordValue = data.ResourceRecordSets[0].ResourceRecords[0].Value;

const isValid = actualIpAddress === awsRecordValue;

let changeCommandResult = null;
if (!isValid) {
  const changeCommand = new ChangeResourceRecordSetsCommand({
    HostedZoneId: hostedZoneId,
    ChangeBatch: {
      Comment: 'modified by n8n workflow',
      Changes: [
        {
          Action: 'UPSERT',
          ResourceRecordSet: {
            Name: recordName,
            Type: 'A',
            TTL: 300,
            ResourceRecords: [
              {
                Value: actualIpAddress
              }
            ]
          }
        }
      ]
    }
  });
  try {
    changeCommandResult = await client.send(changeCommand);
  } catch (ex) {
    console.log(`error`, ex);
    changeCommandResult = ex;
  }
}

return {
  actualIpAddress: actualIpAddress,
  awsRecordValue: awsRecordValue,
  isValid: isValid,
  changeCommandResult: changeCommandResult
};

ついでにウチのネット環境は動的IPのため、上記のようなCodeブロックを組んで定期的に「自宅のIPアドレスが変わったらRoute53のレコードを更新する」ようなスケジュールワークフローを入れています。自宅のIPアドレスを調べるのはHTTP Requestノード等で「http://checkip.amazonaws.com/」を呼べばOK。
死活監視はLambda Functionsで定期的にヘルスのAPIを呼び出して評価してもいいですし、落ちて困らないなら放置しても良い。

後はそもそも2GBのVPS上やらEC2でも十分動いてくれるので自宅サーバーに拘る必要すらも無いのですが、こういうカジュアルなタスクランナーは自宅サーバーを飼っている人たちにこそ必要とされているものなので、是非おすすめしたい所です。