Running a Production Server with Claude Code | Managing 6 Sites on One Home Server

Created on:October 2, 2026 at 2:46 PMReading time: 16 min
thumbnail Image

Letting Claude Code operate a production server means it can take over everything from code fixes to log investigation, but a single command can also take a site down.

I run six websites, including this one (ryusei.io), on a single mini PC at home, and I manage all of them with Claude Code, from code fixes and article publishing to server administration. I wanted a separate development environment, but machine prices had gone up with rising memory and storage costs, so I put it off and use Claude Code directly on the production server. If the budget allows, I plan to add a development machine as well. Because editing the source changes the production files immediately, I have added instruction files and confirmation steps each time an incident happened.

This article explains the whole setup I have built up that way.

What You'll Learn from This Article 

  • How to split CLAUDE.md for a production server
  • How to make Claude Code ask for confirmation before risky operations
  • How to restrict permissions for unattended Claude Code runs
  • How to carry knowledge across sessions
  • How to have Claude Code create articles and images

This article assumes you already know the basics of Claude Code, such as installing it and what CLAUDE.md is for. It does not cover how to build a home server or expose it to the internet.

How I Use Claude Code on a Production Server 

Each site runs in its own Docker container. Visitors reach the home server through a VPS that serves as the IPv4 entry point.

I work with Claude Code by keeping Remote Control (claude rc) running on the server and opening sessions from the Claude Desktop app on my PC or the Claude app on my phone. I can open the same session from either device and continue where I left off. I connect to the server over SSH only when I want to run commands myself or need to work in a terminal.

What is claude rc (Remote Control)?

claude rc (claude remote-control) is a command that lets you control Claude Code sessions running on this machine from claude.ai/code in a browser or from the Claude apps. When you run it in the working folder, it waits for connections, and when you open a session from another device, the session starts on this machine. All file reads and writes and all command execution happen on this machine; the other devices only display the screen and take input.

This machine connects out to Anthropic's servers and waits for instructions, and instructions from other devices arrive through that connection. The other devices never connect to this machine directly, and it works with outbound HTTPS traffic only, so you do not need to open any ports on your router.

To use it, you must be logged in to Claude Code with an account on a Pro, Max, Team, or Enterprise plan (on Team and Enterprise, an administrator must enable Remote Control first). It is not available if you use Claude Code with an API key.

With the default --spawn same-dir, all sessions run in the same folder, so you need to avoid editing the same file from multiple sessions at once. If you want a separate git worktree for each session, use --spawn worktree. A process started over SSH ends when the connection is closed, so I start it inside tmux to keep it running.

Diagram: the Claude apps on a PC or phone connect through Remote Control to Claude Code, and the working folder is bind-mounted into the Docker containers

Working Folder Layout 

I start Claude Code in a single working folder that holds the repositories of all the sites. The working folder itself is a git repository, and each site's repository and the nginx configuration repository are included in it as submodules. With the folder names replaced by examples, the layout looks like this.

plaintext
workspace/                ← folder where Claude Code is started
├── CLAUDE.md             ← constraints and setup shared by all sites
├── .claude/
│   ├── settings.json     ← permissions when started in this folder
│   └── rules/            ← rules loaded only when matching files are opened
├── docs/                 ← change log and open issues
├── memory/               ← Claude Code's memory
├── site-blog/            ← one repository per site
│   └── CLAUDE.md         ← commands and cautions for that site
├── site-tool/
│   └── CLAUDE.md
└── proxy/                ← nginx configuration

Each site's source is served by sharing that site's folder directly with its Docker container (a bind mount), so when Claude Code edits a file, the file inside the production container changes at the same moment. A Next.js site does not change until it is rebuilt, but a process that watches for file changes and restarts automatically restarts as soon as the file is saved.

Verifying Changes End to End with Playwright 

The server has Playwright, a tool for controlling a browser from a program, and Chromium installed. After Claude Code makes a change, the verification is completed on this machine, up to an end-to-end (E2E) check that opens the page, operates it, and confirms the result. Screenshots for articles are taken the same way.

Builds for verification are done after copying the folder to /tmp, so they do not overwrite the build output being served. When I only need to check the screen, I sometimes have Claude Code start a development server on a port separate from production and open it there.

When Claude Code opens my own sites automatically, it opens them with ads and analytics turned off, so that pages opened for verification are not counted as ad impressions or in analytics.

What I Let Claude Code Do 

For each type of work, I separate what Claude Code may do without confirmation from what requires my confirmation before it runs.

Work

Without confirmation

Requires my confirmation

Code changes

Editing, type checks, linting

Production builds and deployment

Articles

Outlines, drafts, diagrams

Draft content, publishing, paid image generation

Investigation

Reading logs, analytics, and the database

—

Verification

E2E checks and screenshots with Playwright

Checks with ads displayed

Server administration

Checking the state of Docker containers and processes

Operations that affect production (see below)

git

—

Commit and push at the end of a unit of work

Splitting CLAUDE.md into Three Layers and Four Kinds of Content 

Looking back at the incidents in production, most were caused not by a wrong command but by Claude Code not having the information about what that command does in this environment. For example, running a type-check command once made pnpm detect a dependency mismatch in its pre-run check and start reinstalling the production dependencies. Another time, editing .env and rebuilding was not enough, and the site went live with the old environment variables that had been fixed when the Docker container was created. Environment-specific behavior like this can only be passed on to Claude Code by writing it in CLAUDE.md or rule files.

Claude Code loads the user-wide ~/.claude/CLAUDE.md and the CLAUDE.md of the folder it starts in at the beginning of a session, and it loads a subfolder's CLAUDE.md when it opens a file in that subfolder. I follow this behavior and split CLAUDE.md into three layers.

Layer

Location

What I write there

User-wide

~/.claude/CLAUDE.md

Response language, how to ask for confirmation, asking about commit and push at the end of a unit of work

Working folder

Root of the working folder

That this is production and which operations need confirmation, the server layout, rules shared by all sites

Per site

Root of each repository

That site's commands, where the source of truth for data and settings lives, cautions specific to that site

Limiting CLAUDE.md to Four Kinds of Content 

At one point, the CLAUDE.md files added up to 2,090 lines. The main reason was that I had been adding background and measurements to CLAUDE.md after every task. So I limited CLAUDE.md to the following four kinds of content and reviewed every file, bringing the total down to 940 lines.

  • Constraints that cause incidents if broken, with one line on what happens
  • Where the source of truth for data, settings, and generated files lives
  • Procedures and commands used every time
  • Pointers to detailed documents, with when to read them

Background, measurements, and ideas that did not work go into the change log, and unresolved problems go into the open issues file. CLAUDE.md itself also says to keep each item within three lines and, when in doubt, to decide by asking whether the item is needed in every session.

Rules That Load Only When a Matching File Is Opened 

Rules that are needed only when touching specific files, such as the procedure for changing the nginx configuration, go in the working folder's .claude/rules/. If you write paths at the top of the file, the rule is loaded only when a file matching the pattern is opened.

markdown
---
paths:
  - "proxy/conf.d/**"
  - "proxy/nginx.template.conf"
---

# Read before changing the nginx configuration

- nginx.conf is a generated file, so do not edit it directly. Edit the template and regenerate it
- Ask for confirmation before reloading the configuration

When I checked with the hook that runs when rules are loaded (InstructionsLoaded), a rule was loaded only when a file was opened with Claude Code's Read tool, not when the file was read with cat in Bash. If a rule's body pulls in another file with @, that part is loaded at the start of the session, before any file is opened, so I write content I want loaded later directly in the rule body.

I move rules here that are not needed every time but cause incidents if overlooked. However, since the trigger is limited to opening a file, cautions for operations that do not involve opening a file, such as restarting a Docker container, stay in the main CLAUDE.md.

Making Claude Code Ask Before Risky Operations 

Prohibitions written in CLAUDE.md are not always followed. So besides listing the operations that need confirmation in CLAUDE.md, I define how confirmation is requested and re-inject those rules with hooks every time.

Operations That Always Need Confirmation 

At the top of the working folder's CLAUDE.md, I wrote that the following operations always require confirmation before they run.

  • Stopping, recreating, or restarting Docker containers
  • Restarting, stopping, or changing the number of processes
  • Applying database migrations
  • Deleting files, volumes, or images
  • Changing and reloading the nginx configuration, replacing SSL certificates
  • git operations that rewrite the working folder (checkout, reset, pull, clean)

I also state explicitly that read-only operations, such as viewing logs or process status, do not need confirmation. Being asked for confirmation every time gets tiresome, so I limit confirmation to operations that affect production.

Asking with a Recommended Option 

How confirmation is requested is defined in the user-wide CLAUDE.md. Claude Code has a feature for asking questions with options (AskUserQuestion), and I instruct it to put the recommended option first and add one line of reasoning to each option.

Because the question comes with options, I can answer with a single choice if the recommendation is fine, and if none of them fit, I can write a free-form answer under "Other". Claude Code asks in these situations: commit and push, decisions that are hard to reverse later such as design direction, cases where the specification can be read in more than one way, and deleting files or deploying to production.

Confirming Commit and Push Once per Unit of Work 

I used to have a hook ask for permission on every commit and push. Being asked every time became tiresome, so I removed it. Now commit and push can run without a permission prompt, and Claude Code asks about commit and push together, once, when a unit of work is finished.

If Claude Code tries to end its response without asking, a Stop hook (a hook that runs just before Claude Code finishes its response) checks for uncommitted or unpushed changes and stops it. It does not stop twice for the same state, so it does not keep stopping on changes I have answered "do not commit" for.

The git status that this hook runs rewrites git's index. Because of that, a process that watches for file changes and restarts automatically was restarted every time the hook ran. Now the hook runs git with GIT_OPTIONAL_LOCKS=0 so that it does not rewrite the index, and .git is also excluded from the watch targets.

Hooks and Permission Settings 

Rules I always want followed, such as how to ask for confirmation, are kept in one file, and a UserPromptSubmit hook (a hook that runs every time I send a message) attaches them to every message. When I edit the rule file, the change applies from the next message. The confirmation-related part of the user-wide settings (~/.claude/settings.json) looks like this.

json
{
  "permissions": {
    "allow": ["Bash(git add:*)", "Bash(git commit:*)", "Bash(git push:*)"]
  },
  "hooks": {
    "UserPromptSubmit": [
      { "hooks": [{ "type": "command", "command": "$HOME/.claude/hooks/inject-rules.sh", "timeout": 10 }] }
    ],
    "Stop": [
      { "hooks": [{ "type": "command", "command": "$HOME/.claude/hooks/check-git.sh", "timeout": 15 }] }
    ]
  }
}

Operations allowed without confirmation are split between the user-wide settings and the working folder's settings (.claude/settings.json). High-impact ones, such as connecting to an external server, are allowed without confirmation only in sessions started in the working folder.

All of this is for making Claude Code ask for confirmation; none of it blocks risky operations in the settings. If you want to block them reliably, you can list the commands under ask or deny in permissions, or block them with a PreToolUse hook, which runs before a command is executed. I have not added either yet.

Restricting Permissions for Unattended Claude Code Runs 

I used to run Claude Code unattended every morning with cron (claude -p) to research article topics and leave material for writing in a folder. The suggested topics were not good enough, so I have stopped it. I now choose topics myself.

When I built it, I found that unattended runs load the same user and project settings as interactive use. As a result, the permission to commit and push without confirmation, along with the Stop hook, carried over to the unattended runs. Because unattended Claude Code reads external web pages, if it had followed instructions written on a page (prompt injection), it could have used those permissions.

So for unattended runs, I pass a separate, narrower configuration instead of the interactive one.

  • --restricted keeps user and project settings from loading and removes the tools that run commands (tools named with --tools remain available)
  • A dedicated settings file (--settings) limits writes to one working folder and command execution to my own scripts
  • --permission-mode dontAsk denies every operation that would need confirmation unless it is on the allow list (reads still go through, so files containing secrets are blocked with deny)
  • Auto memory (a feature where Claude Code writes down what it learned in files and loads them in the next session) is turned off. The storage location is determined per git repository, so leaving it on would let the run write to the interactive memory
  • The database and git are handled by preprocessing scripts, and Claude Code only reads the resulting files
bash
claude -p --restricted \
  --tools Read Glob Grep Write Edit WebFetch WebSearch Bash \
  --settings ./config/headless-settings.json \
  --permission-mode dontAsk \
  --output-format json < prompt.md

After settling the configuration, I used dummy files to confirm that writing outside the working folder, reading files that contain secrets, and running git are all denied.

Carrying Knowledge Across Sessions 

A new session does not contain the conversation from the previous one. Before finishing its work, Claude Code writes the information needed in the next session to files, split into four places depending on the content.

Where

What

Example

CLAUDE.md

Constraints, sources of truth, and procedures needed every time

Do not delete the production build output

Change log

Background, measurements, ideas that did not work

Why a setting was chosen and what was measured at the time

Open issues

Problems not fixed yet and planned checks

The date to check whether a fix worked

Memory

My preferences and ongoing projects

Write without metaphors

For memory, I use the auto memory feature, with its storage location moved into the working folder with the autoMemoryDirectory setting and managed with git.

When a task is done, Claude Code writes what it learned into these categories and lists the updated files in its completion report. When a session starts, a SessionStart hook shows failed backups and items awaiting confirmation.

Writing Articles and Creating Images with a Skill 

Recent articles on this site are written by Claude Code following procedures kept in a skill (a file that bundles procedures and reference material, which Claude Code loads when needed). The skill covers everything from choosing a topic to style rules, article structure, image creation, and posting to the CMS. Three of these steps directly affect the content and look of an article, so they are always performed.

Interviewing Me Before Writing 

The skill includes a step where, after the outline is set and before the body is written, Claude Code asks me the following six questions.

  • Where I failed first
  • Options I dropped along the way, and why
  • Measurements on hand (time taken, cost, specs, versions)
  • Points where I got stuck that the official documentation does not cover
  • Whether I still use it or switched to something else
  • What readers are likely to misunderstand

I do not let it write about anything I have not experienced, so without these answers the article would be a generic piece anyone could have written. Before the interview, Claude Code checks what it can learn from the code, CLAUDE.md, and the change log on its own, writes down its guesses, and asks me only to confirm them.

Comparing the Writing Style with My Articles by the Numbers 

The style rules are based on figures measured from 14 articles I wrote myself in Japanese. For example, my sentences average 52 characters, while the articles Claude Code wrote when the skill was first created averaged 31.7 characters, with short sentences of similar length lined up one after another.

A bundled checker compares each draft with my articles on sentence-length variation and how often Japanese quotation brackets (「」) and the word for "I" appear, and it also checks for words I do not use. I read the draft only after every item is within range. Places where I think "I would not write it this way" are added to a "written like this / write it like this" table.

Even so, when I checked 12 articles written by Claude Code again, I found 18 factual errors and descriptions that had become outdated after the tools changed. The outdated descriptions were cases where I changed a tool's behavior after the article was written but did not update the article. Now, opening a file that changes a tool's screens or limits loads a rule that asks for the article describing that behavior to be updated as well.

Drawing Images as SVG and Checking Them at Small Size 

Thumbnails and diagrams are drawn by Claude Code as SVG and converted to PNG or WebP with a small tool I wrote. This server has no Japanese fonts installed, so converting as-is turns every Japanese character into "□"; the tool bundles Noto Sans JP and loads it only during conversion.

Claude Code can load the converted image and check its content, so I have it also look at the thumbnail scaled down to the size shown in the article list and confirm that the text is readable.

Only when a photo-like image is needed do I use a separate tool that calls the API of Google's image generation model, Nano Banana Pro. It charges per image, so Claude Code shows a cost estimate before running it and generates the image only after I confirm.

Splitting Research and Review across Subagents 

Research and reviews are split across subagents (Claude Code instances that run in a separate context) and run in parallel.

Choosing Models for Subagents 

I am on Claude's Max 20x plan. I use Claude Code on the same account from morning to night, both for running this server and for developing other projects. Recently I have been using the workflow feature, which runs multiple subagents in a fixed sequence on large codebases, more often, and even Max 20x is starting to run short. If the budget allows, I would like to add a second account.

Once, I started six subagents at the same time without specifying a model, and they ran on Fable, the same model as the parent session, using up a large share of my usage. Fable is the model with the highest price per token. Since then, I let Claude Code choose the model, but it must get my approval before using Fable.

The model that an alias such as sonnet points to is determined by the Claude Code version. In my environment, automatic updates for Claude Code were turned off, so even after a new Sonnet was released, subagents that specified sonnet kept running on the previous Sonnet. The resident Remote Control process also keeps running on the version it was started with, so after updating Claude Code, the resident process needs to be restarted as well. A cron script now checks once a week whether a new version is out.

Having Another Subagent Review My Own Diff 

After fixing a batch of bugs, I have another subagent review the diff of those fixes once more. When I fixed 59 bugs found in a review of one of my tool sites at once, this diff review found 6 new bugs introduced by the fixes. All of them had passed type checks and linting. Two of them recreated, in a different form, the problem the fix was meant to solve.

The diff review happens before the fixes are committed. Claude Code pulls the pre-fix files with git show HEAD:<path> and compares them to confirm that the diff really introduced each problem.

Summary 

Most incidents on the production server were caused by environment-specific behavior that had not been passed on to Claude Code. Each time one happened, I added a rule: I write it in CLAUDE.md, define how confirmation is requested, and re-inject those rules with hooks every time.

  • Split CLAUDE.md into three layers (user-wide, working folder, per site) and limit it to four kinds of content: constraints, sources of truth, procedures, and pointers
  • Ask for confirmation only for operations that affect production, using options with a recommendation
  • For unattended runs, do not load the interactive settings; pass a separate, narrower configuration
  • Have Claude Code write background to the change log and preferences and ongoing projects to memory, and carry them over to the next session
  • Have Claude Code write articles following the skill's procedure, interview me before writing, and compare the style with measurements from my own articles

Related articles 

Guide on deploying multiple Next.js applications with different port numbers on the same server | Ryusei.IO

This guide explains how to deploy multiple Next.js web applications on the same server using reverse proxy functionality, with Open LiteSpeed as the reverse proxy solution.

faviconryusei.io
How to Deploy Next.js on an On-Premises Environment | Using Conoha VPS | Ryusei.IO

This guide explains how to deploy web applications and services developed with Next.js to on-premise environments like VPS servers or self-hosted systems. While this approach requires more manual effort compared to using Vercel, it offers significant cost savings and greater operational flexibility.

faviconryusei.io
[Easy] How to Easily Assign a Global IP Address to Your Home Server or Virtual Machine | Ryusei.IO

This guide explains how to directly connect a local server running at home or a Linux OS container on a virtual machine to PPPoE and assign a fixed global IPv4 address.

faviconryusei.io

Latest Tips