iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🐋

Building a Comfortable Zenn Writing Environment with Dev Containers

に公開

Introduction

I set up this environment because I was drawn to the benefits of version control via GitHub integration and the ability to create articles in VSCode.

Setting it up normally takes some effort, as you need to install Node.js and Zenn CLI and configure VSCode (along with its extensions) specifically for Zenn. Additionally, I prefer to keep my local environment as clean as possible.

Therefore, I built a Zenn article writing environment using Dev Containers.

The main benefits are as follows:

  • Keep the local environment clean by isolating application dependencies within a container
  • Separate VSCode settings and Markdown-related extensions into a dedicated environment for Zenn writing
  • Manage environment setup procedures through code (Dockerfile, devcontainer.json, etc.)
  • Reproduce the same writing environment across different machines

Overview

This article explains the following topics in order:

  • Environment setup procedures using the VSCode extension "Dev Containers"
  • Actual usage and an introduction to useful features
  • Discussion on various settings and the selection of extensions

Environment Setup

I have published a template for environment setup on GitHub.
If you find it tedious to create the configuration files yourself, please use this.

https://github.com/ryuryu333/zenn_contents_env_template

Final folder structure
zenn_contents/
├── .git/
├── .devcontainer/
│  ├── devcontainer.json
│  ├── docker-compose.yml
│  └── Dockerfile
├── articles/
│  └── article_name.md
└── images/
    └── article_name/
        └── image.webp

Prerequisites

It is assumed that Docker, Git, and VSCode Dev Containers are already installed.
Since installation methods are explained in various places, I will omit them here.

https://docs.docker.jp/desktop/install/windows-install.html

https://git-scm.com/book/ja/v2/使い始める-Gitのインストール

https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers

The commands shown are based on Windows 11. If you are using a different OS, please adapt them accordingly.

Zenn and GitHub Integration

Please refer to the following official guide to create and link your repository.

https://zenn.dev/zenn/articles/connect-to-github

Creating Dev Containers Configuration Files

Create a .devcontainer folder directly under your local repository (example: zenn_contents). Create the configuration files for Dev Containers within the .devcontainer folder, referring to the structure below.

Folder structure
zenn_contents/
├── .git/
└── .devcontainer/
    ├── devcontainer.json
    ├── docker-compose.yml
    └── Dockerfile

devcontainer.json

Specifies which docker-compose.yml to use. It also defines the extensions and settings used in VSCode.

devcontainer.json
{
    "name": "zenn_contents",
    "dockerComposeFile": "docker-compose.yml",
    "service": "zenn_contents",
    // Specify the folder to open when connecting with VSCode
    "workspaceFolder": "/work/zenn_contents",
    // Specify the VSCode environment
    "customizations": {
        "vscode": {
            // Specify extensions to install
            "extensions": [
                "GitHub.copilot",
                "GitHub.copilot-chat",
                "negokaz.zenn-editor",
                "nodamushi.udon"
            ],
            // Define VSCode settings
            "settings": {
                // udon extension settings
                // When an image is pasted into a .md file in the articles directory,
                // save the image to workspace/image/article_name/.
                // Image links are created as image/... instead of relative links (compatible with Zenn specifications).
                "udon.baseDirectories": [
                    ["articles/*.md", "${workspaceFolder}/images/$fileBasenameNoExtension"]
                ],
                "udon.rule": [
                    ["articles/*.md", "![](/${relImage:${workspaceFolder}})"],
                    ["*.md", "![](${relImage})"],
                    ["*", "${relImage:${workspaceFolder}}"]
                ]
            }
        }
    }
}

docker-compose.yml

This file and the Dockerfile are written in the same way as standard Docker. While docker-compose is used here to set up folder sharing between the container and the local machine, the same can be configured in devcontainer.json. Personally, I prefer to let Docker handle what it can and use docker-compose.yml primarily for VSCode-related settings.

docker-compose.yml
services:
  zenn_contents:
    build:
      dockerfile: Dockerfile
    tty: true
    # Mount all folders for Zenn
    volumes:
      - ../:/work/zenn_contents/

Dockerfile

Dockerfile
# Choose a lightweight alpine image
FROM node:22-alpine3.20
ENV NODE_PATH /opt/node_modules
# Install openssh-client as SSH connection is required when using git inside the container
RUN apk update && \
    apk add --no-cache git bash openssh-client
# Install zenn-cli
WORKDIR /work
RUN npm init --yes && \
    npm install -g zenn-cli@latest
# Create a folder to manage actual articles
# Folder shared between the container and the host
WORKDIR /work/zenn_contents

SSH Settings

It is convenient to be able to do everything from creating Zenn articles to publishing them within the container. However, additional configuration is required to communicate with GitHub via SSH connection.

https://code.visualstudio.com/remote/advancedcontainers/sharing-git-credentials#_using-ssh-keys

Currently, executing git push inside the container results in an error because the private key does not exist. Dev Containers provides a feature to use SSH credentials inside the container if there is an ssh-agent running locally, which we will use.

Configure the ssh-agent to start automatically when Windows starts.

PowerShell
# Set the SSH agent service to start automatically
Set-Service ssh-agent -StartupType Automatic
# Start the SSH agent service
Start-Service ssh-agent

If your GitHub private key is not registered in the ssh-agent, follow these steps to register it:

PowerShell
# Check registered private keys
ssh-add -l
# Register the private key in the .ssh directory (e.g., ~/.ssh/id_ed25519)
ssh-add
# To register a specific private key
ssh-add ~/.ssh/GitHub/id_ed25519

Usage

Operations for the First Startup

Starting the Container

Open the article writing directory in VSCode. Next, select Rebuild Container Without Cache and Reopen in Container from the command palette.

VSCode command palette

The Docker container build will start automatically. Wait a few seconds until the "Connecting to Dev Container" message in the bottom right disappears.

Reloading Extensions

Only for the first time, extensions may not load correctly. This can be resolved by exiting and re-entering the container.

To exit the container, perform one of the following:

  • Select the article writing directory from "File" > "Open Recent".
  • Click the blue >< mark in the bottom left of the screen and select "Close Remote Connection" from the command palette.

VSCode Open Recent

VSCode Container Exit Button

Methods to enter an already created container are as follows:

  • Select the directory labeled "[Dev Container]" from "File" > "Open Recent".
  • Select "Reopen in Container" from the command palette.

Initializing Zenn CLI

In this state, the browser preview function will not work, so execute the following command inside the container.

sh
npx zenn init

https://zenn.dev/zenn/articles/install-zenn-cli

With this, the construction of the writing environment is complete.

Detailed Operations

Pasting Images

You can paste images using Ctrl + Alt + V.

  • Copying and pasting image files
  • Pasting screenshots

Both are supported.

The link format within the .md file automatically becomes the Zenn specification: ![](/images/article_name/image_name).

Images are saved in the following folder.

Folder structure
zenn_contents/
├── articles/
│   └── article_name.md
└── images/
   └── article_name/
        └── image.webp

Image pasting is provided by the udon extension. Please check below for details.

https://zenn.dev/nodamushi/articles/db57ccd5a6d5ba

Explorer and Preview Features

Normally, the .md filenames of Zenn articles do not match their titles.

https://zenn.dev/zenn/articles/what-is-slug

However, when you expand ZENN CONTENTS in the Explorer, the article files are displayed by their titles, making them easier to manage.

VSCode Explorer

VSCode Explorer Extension

A button with a "Z" mark is added next to the regular preview button.
(It's the second button from the left at the top right of the .md file window.)

Clicking this will display a Zenn-specific preview within VSCode, similar to the preview in a web browser.

VSCode Preview Button

Zenn Preview Screen

These features are provided by the Zenn editor extension.
Please check below for more details.

https://github.com/negokaz/vscode-zenn-editor/tree/main

When Modifying Configuration Files

If you change devcontainer.json or Dockerfile, please execute Rebuild Container Without Cache and Reopen in Container from the command palette.

Selection of Settings and Extensions

In this chapter, I would like to provide information for those who want to customize their Dev Containers settings to their liking.
I will briefly discuss VSCode settings, selection of extensions, and the problems I encountered during environment setup along with their solutions.

VSCode Settings

Basically, your usual local settings are inherited as they are, so you only need to describe the parts you want to change in devcontainer.json.

You can check your current settings in JSON format by opening settings with Ctrl + , and clicking the file button in the top right.
Copying and pasting these contents into the settings of devcontainer.json as needed makes editing easier.

VSCodeSettingJSON

Managing Zenn-specific settings, such as those for the udon extension, by isolating them in devcontainer.json rather than relying on local settings is effective in preventing unnecessary trouble.

VSCode Extensions

My extension configuration is relatively simple.

devcontainer.json
{
    "customizations": {
        "vscode": {
            "extensions": [
                "GitHub.copilot",
                "GitHub.copilot-chat",
                "negokaz.zenn-editor",
                "nodamushi.udon"
            ]
        }
    }
}

It is also recommended to add extensions such as linters and formatters based on the following reference articles.

https://zenn.dev/kiku09020/articles/vscode-markdown-kkp

https://zenn.dev/hrtk/articles/zenn-textlint-prettier

https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one

https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint

Since these extensions and their settings can also be centrally managed in devcontainer.json, you can customize your environment specifically for Zenn without being affected by other development environments.
This is exactly why I especially like Dev Containers.

What Was Difficult About the Environment Setup

Extensions Don't Become Active!

When I reconstructed the container by executing Rebuild Container Without Cache and Reopen in Container, the zenn editor extension did not become Active.
Executing Reopen in Container makes it Active, but since the root cause hasn't been resolved, it remains a bit of a nagging issue.

In the initial draft, I had written settings to use VSCode's standard features for pasting images.

https://code.visualstudio.com/docs/languages/markdown#_inserting-images-and-links-to-files

devcontainer.json
{
    "customizations": {
        "vscode": {
            "settings": {
                // Old settings
                "markdown.copyFiles.destination": {
                    "/articles/**/*": "images/articles/${documentBaseName}/"
                }
            }
        }
    }
}

However, with this setting, when an image is pasted into a .md file, the link is created like ![](../images/~).
As a result, I would have to manually rewrite it to the Zenn specification ![](/images/~), which is extra effort.

https://zenn.dev/zenn/articles/deploy-github-images

Also, extensions like "paste image" exist, but they don't work in WSL environments, so they aren't suitable for Windows users.

https://zenn.dev/melon1891/articles/setting_for_zenn

Note that there are ways to force them to work, but the configuration tends to become complex.

https://another.maple4ever.net/archives/3457/

That's when I found the udon extension.
To put it mildly, it's a god-tier extension.
It significantly reduced the effort of configuration.

https://zenn.dev/nodamushi/articles/db57ccd5a6d5ba

Conclusion

This article led me to switch from writing in the browser to writing in VSCode.
As expected, the VSCode I'm used to is more efficient and comfortable for writing.

In the future, I plan to gradually customize it to my liking by adding extensions and realizing ideas for "it would be nice if it were like this."

Discussion