✍️

Docker + VSCode で Zenn で記事を書く環境を作る

2021/03/22に公開

目標

Zenn で記事を執筆するために、以下のような環境を作ります。

  • Zenn CLI を使って記事を管理
  • VSCode で記事を執筆
  • textlint を導入し、文章をレビューする
  • これらを Docker で環境を構築する

完成すると、このようになります。

実際に、Hiroya-W/zenn-docsで利用し、この記事も執筆されています。

環境構築

ここではホスト側の ~/zenn-docs をワークスペースとして環境構築を進めていくことにします。

Host$ mkdir ~/zenn-docs
Host$ cd ~/zenn-docs

node.js用のpackage.json、package-lock.jsonを生成する

以降の手順を行うために package.jsonpackage-lock.json が必要になります。
それらを生成するために node.js の環境が必要になりますが、ホスト側には node.js の環境がありません。
そこで、node.js のコンテナイメージを用いて、生成させることにします。

Dockerfile

コンテナイメージを作成するための Dockerfile を記述します。
dockerディレクトリを作成し、その中に Dockerfile を作成します。

Host$ mkdir ~/zenn-docs/docker
Host$ vim ~/zenn-docs/docker/Dockerfile
Dockerfile
FROM node:lts-alpine

USER node

WORKDIR /workspaces/zenn-docs

docker-compose.yml

用意した Dockerfile を用いて、コンテナを起動する方法を docker-compose.yml に記述します。

Host$ vim ~/zenn-docs/docker-compose.yml
docker-compose.yml
version: '3'
services:
  main:
    build: ./docker
    volumes:
      - type: bind
        source: "."
        target: "/workspaces/zenn-docs"
        consistency: "cached"

ホスト側のディレクトリ . を、コンテナ内の /workspaces/zenn-docs にマウントします。
これで、コンテナ内で作成した package.jsonpackage-lock.json がホスト側に配置することが出来ます。

コンテナを起動する

コンテナを起動し、そのコンテナで /bin/ash を実行します。

Host$ docker-compose run --rm main /bin/ash
...
/workspaces/zenn-docs $

package.jsonpackage-lock.json を生成します。

/workspaces/zenn-docs $ npm init --yes && npm install
/workspaces/zenn-docs $ ls
docker   docker-compose.yml   package-lock.json   package.json

Zenn CLI と textlint 関連のパッケージをインストールし、package.jsonpackage-lock.json にパッケージ情報を追加します。

/workspaces/zenn-docs $ npm install \
  zenn-cli \
  textlint \
  textlint-rule-preset-ja-spacing \
  textlint-rule-preset-ja-technical-writing \
  textlint-rule-spellcheck-tech-word

Zenn用のディレクトリ構成を作成する

articlesbooks などの Zenn 用のフォルダやファイルを生成しておきます。

/workspaces/zenn-docs $ npx zenn init

Zennの記事を執筆するためのコンテナを作成する

ここからは、Zenn CLI を用いて記事を執筆するためのコンテナイメージを作成していきます。

先程作成した package.jsonpackage-lock.json を用いてコンテナを作成できるようにし、Zenn 用のディレクトリを Docker コンテナにマウントして利用できるようにします。

前準備

package.jsonpackage-lock.jsonDockerfile から扱えるように、ファイルの配置位置を docker フォルダ内に変更しておきます。

Host$ cd ~/zenn-docs
Host$ mv package.json package-lock.json docker

また、ホスト側には node_modules フォルダは不要なので、削除しておきます。

Host$ cd ~/zenn-docs
Host$ rm -rf node_modules

Dockerfile

まずは、先程作成した、Dockerfileを編集します。

Host$ vim ~/zenn-docs/docker/Dockerfile
Dockerfile
FROM node:lts-alpine

RUN apk update && \
    apk --no-cache add git && \
    apk --no-cache add openssh

WORKDIR /workspaces/node_app

COPY ./package.json ./package-lock.json ./

RUN npm install --no-optional && npm cache clean --force

ENV PATH /workspaces/node_app/node_modules/.bin:$PATH

USER node

WORKDIR /workspaces/zenn-docs

コンテナのビルド時に、用意した package.jsonpackage-lock.json を用いてパッケージのインストールをしています。
この時、/workspaces/node_app/node_modulesにパッケージがインストールされます。

また、USER nodeを指定し、 一般ユーザを利用するようにしています。

一般ユーザを利用する理由

通常、コンテナ内から作成した記事などのファイルは root ユーザで作成されます。
しかし、ホストでは一般ユーザであることがほとんどで、一般ユーザではルートユーザで作成したファイルを編集することは出来ません。

今回利用するイメージには、ホストの一般ユーザと同じ uid と gid を持つユーザ(1000:1000)として node ユーザが存在します。
nodeユーザとしてコンテナ内で操作をすることで、この問題を解決することが出来ます。

用意されているユーザの一覧は次のようにして確認できます。

USER nodeの記述を削除した状態で、コンテナをビルドして root ユーザとしてコンテナにアタッチしておきます。
用意されているユーザ一覧は /etc/passwd で見ることが出来ます。

root# cat /etc/passwd

...
node:x:1000:1000:Linux User,,,:/home/node:/bin/sh

docker-compose.yml

用意した Dockerfile を用いて、コンテナを起動する方法を docker-compose.yml に記述します。

Host$ vim ~/zenn-docs/docker-compose.yml
docker-compose.yml
version: '3'
services:
  main:
    build: ./docker
    image: zenn-base
    container_name: zenn-docs
    volumes:
      - type: bind
        source: "."
        target: "/workspaces/zenn-docs"
        consistency: "cached"
    ports:
      - "8000:8000"
    tty: true

Zenn の記事データはホスト側に置き、そのディレクトリ . を、コンテナ内の /workspaces/zenn-docs にマウントします。

devcontainer.json

VSCode の Remote Container 用の設定を記述します。

まずは、VSCode で ~/zenn-docs フォルダを開きます。

コマンドパレットから、> Remote-Containers: Add Development Container Configuration Files...From 'docker-compose.yml'を選択し、devcontainer.jsonを生成します。

.devcontainerフォルダに、devcontainer.jsondocker-compose.yml ファイルが生成されます。

このうち、docker-compose.ymlファイルは必要ないので、削除しておきます。

Host$ rm .devcontainer/docker-compose.yml

devcontainer.jsonを以下のように修正します。

devcontainer.json
{
    "name": "Zenn Docs",
    "dockerComposeFile": [
        "../docker-compose.yml",
    ],
    "service": "main",
    "workspaceFolder": "/workspaces/zenn-docs",
    "settings": {
        "terminal.integrated.profiles.linux": {
            "ash": {
                "path": "/bin/ash",
            }
        },
        "terminal.integrated.defaultProfile.linux": "ash",
        "textlint.nodePath": "/workspaces/node_app/node_modules/",
    },
    "extensions": [
        "taichi.vscode-textlint",
        "negokaz.zenn-editor"
    ]
}

VSCode の Remote Container で開いた際のワークスペースを workspacceFolder に指定し、ここでは workspaces/zenn-docs にしています。
workspaces/zenn-docsは、Dockerfileに書いていたように、ホスト側の Zenn の記事データをコンテナ内にマウントしたディレクトリです。

また、VSCode の拡張機能としてvscode-textlintzenn-editorをコンテナ側にインストールします。

.textlintrc

textlint 用の設定ファイルを配置します。記述する内容は、以下のサイトからお借りしました。

https://zenn.dev/serima/articles/4dac7baf0b9377b0b58b#導入手順

host$ vim ~/zenn-docs/.textlintrc

SSH agent

コンテナ内でホストの SSH key を用いて Git が使えるようにしておきます。
これで、いつもどおり GitHub のリモートリポジトリに対して git pullgit push が出来るようになります。

ここでは、公式ドキュメントに従って進めていきます。

https://code.visualstudio.com/docs/remote/containers#_using-ssh-keys

ホスト側にパッケージを導入します。

# Ubuntuの場合
Host$ sudo apt update
Host$ sudo apt install openssh-client socat

# Arch Linuxの場合
Host$ sudo pacman -Syu
Host$ sudo pacman -S openssh socat

次に、ssh-agentをバックグラウンドで実行しておきます。

Host$ eval "$(ssh-agent -s)"

SSH key を SSH agent に追加します。
なお、ここでは、秘密鍵が id_rsa というファイル名で生成されていることを前提としています。

Host$ ssh-add $HOME/.ssh/id_rsa

次に、~/.bash_profileに以下を追記します。
これで、常に SSH agent が実行されるようにしておきます。

~/.bash_profile
if [ -z "$SSH_AUTH_SOCK" ]; then
   # Check for a currently running instance of the agent
   RUNNING_AGENT="`ps -ax | grep 'ssh-agent -s' | grep -v grep | wc -l | tr -d '[:space:]'`"
   if [ "$RUNNING_AGENT" = "0" ]; then
        # Launch a new instance of the agent
        ssh-agent -s &> $HOME/.ssh/ssh-agent
   fi
   eval `cat $HOME/.ssh/ssh-agent`
fi

一旦再起動した後、ssh-add -lコマンドを実行した際に、登録した鍵が表示されていればOKです。

Host$ ssh-add -l
2048 SHA256:... /home/user/.ssh/id_rsa

コンテナをビルドしVSCodeをアタッチする

ファイルを配置出来れば、Remote Container の機能を用いて Docker コンテナをビルド・起動し、VSCode にアタッチします。
コマンドパレットから > Remote-Containers: Reopen in Container を実行します。

VSCode にアタッチできると、左下の表示が Dev Container:Zenn Docs に変化します。
また、ターミナルから Docker コンテナ内の環境でコマンドを叩くことが出来るようになります。

例えば、idコマンドを実行すると、nodeユーザになっていることが分かります。
lsすると、コンテナ内にマウントされた docker ディレクトリや docker-compose.yml が表示されます。

また、コンテナ内で ssh-add -l した際に同じ鍵が表示されるか、GitHub に接続できるかもを確認しておきます。

node$ ssh-add -l
2048 SHA256:... /home/user/.ssh/id_rsa
node$ ssh -T git@github.com
Hi ...! You've successfully authenticated, but GitHub does not provide shell access.

Zenn CLIを使う

VSCode 上でターミナルから Zenn CLI を利用することが出来ます。
新しく記事を作成し、ブラウザでプレビューするには次のようにします。

node$ npx zenn new:article
node$ npx zenn preview

これで、以下の URL でプレビューを確認することが出来ます。

http://localhost:8000/

おわりに

今回は、Docker で Zenn CLI と textlint を実行する環境を構築し、そのコンテナを VSCode で用いて Zenn の記事を執筆する環境を構築してみました。

今回のように、textlint をローカルで実行するのではなく、GitHub Actions で実行するようにしても良いでしょう。また、VSCode の Markdown 拡張機能をインストールすると、より快適になりそうです。
記事を書きつつ、更に快適な執筆環境を整えてみたいですね。

参考文献

環境構築にあたり、参考にさせていただいた資料を記載しておきます。

GitHubで編集を提案

Discussion