Skip to content

Adding a Blog

Step-by-step guide to add a 4th (or Nth) blog to the platform.

Time estimate: 30-60 minutes Requires: Server SSH access, Ghost MCP restart, n8n access

Prerequisites

  • [ ] Domain name (or camlab.dev subdomain) with DNS pointing to server
  • [ ] Blog niche/topic decided
  • [ ] Brand guidelines written (target audience, tone, image style)
  • [ ] Reddit subreddits identified for research

Step 1: Choose a Short Name

Pick a short identifier (lowercase, no spaces/hyphens). Used everywhere as a key.

Example: newblog

This will appear in: container names, database names, directory paths, env vars.

Step 2: MySQL Database

ssh camlab
docker exec -it mysql mysql -u root -p

CREATE DATABASE ghost_newblog;
CREATE USER 'ghost_newblog'@'%' IDENTIFIED BY '<generate-password>';
GRANT ALL ON ghost_newblog.* TO 'ghost_newblog'@'%';
FLUSH PRIVILEGES;
EXIT;

Step 3: Environment Variables

Add to /opt/camlab/.env:

# Ghost DB - NewBlog
GHOST_NEWBLOG_DB_USER=ghost_newblog
GHOST_NEWBLOG_DB_PASSWORD=<the-password-from-step-2>
GHOST_NEWBLOG_DB_NAME=ghost_newblog

# Ghost URL
GHOST_NEWBLOG_URL=https://blog.newblog.camlab.dev

# Ghost API Keys (fill after step 5)
GHOST_NEWBLOG_ADMIN_API_KEY=
GHOST_NEWBLOG_CONTENT_API_KEY=

# Ghost MCP
GHOST_NEWBLOG_ADMIN_URL=http://ghost-newblog:2368
GHOST_NEWBLOG_CONTENT_URL=http://ghost-newblog:2368
GHOST_NEWBLOG_MCP_TOKEN=<openssl rand -hex 16>

Update GHOST_INSTANCES:

GHOST_INSTANCES=cam4,cam4models,cam4pays,newblog

Step 4: Docker Compose

Add to docker-compose.yml:

  ghost-newblog:
    image: ghost:6-alpine
    container_name: ghost-newblog
    restart: unless-stopped
    depends_on:
      - mysql
    ports:
      - "127.0.0.1:2371:2368"
    environment:
      url: ${GHOST_NEWBLOG_URL}
      database__client: mysql
      database__connection__host: mysql
      database__connection__port: 3306
      database__connection__user: ${GHOST_NEWBLOG_DB_USER}
      database__connection__password: ${GHOST_NEWBLOG_DB_PASSWORD}
      database__connection__database: ${GHOST_NEWBLOG_DB_NAME}
      NODE_ENV: production
    volumes:
      - ./ghost/newblog:/var/lib/ghost/content
    networks:
      - camlab_network

  runner-newblog:
    build: ./runners
    container_name: runner-newblog
    restart: unless-stopped
    volumes:
      - ./runners/newblog/claude-config:/root/.claude
      - ./runners/newblog/workspace:/workspace
    environment:
      - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
      - RUNWARE_API_KEY=${RUNWARE_API_KEY}
    networks:
      - camlab_network

Step 5: Ghost Setup

cd /opt/camlab
docker compose up -d ghost-newblog
  1. Visit https://blog.newblog.camlab.dev/ghost
  2. Complete setup wizard
  3. Create integration: Settings → Integrations → Add Custom → "CamLab Automation"
  4. Copy Admin API Key and Content API Key into .env

Step 6: Theme

# Copy and customize a theme
cp -r /opt/camlab/ghost/cam4/themes/camlab-casper-cam4 \
      /opt/camlab/ghost/newblog/themes/camlab-casper-newblog

# Edit package.json to change name and settings
# Activate in Ghost Admin → Design → Themes

Step 7: Ghost MCP

Restart to pick up new instance:

docker compose restart ghost-mcp

Verify:

curl -X POST http://localhost:3002/<newblog-mcp-token> \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Step 8: Claude Code Runner

# Create runner config
mkdir -p /opt/camlab/runners/newblog/{claude-config/{agents,config},workspace}

# Copy agent templates and customize
cp /opt/camlab/runners/cam4/claude-config/agents/*.md \
   /opt/camlab/runners/newblog/claude-config/agents/

# Create brand guidelines for new blog
# Edit: /opt/camlab/runners/newblog/claude-config/config/brand-guidelines.json

# Create MCP config with newblog Ghost token
cat > /opt/camlab/runners/newblog/claude-config/.mcp.json << 'EOF'
{
  "mcpServers": {
    "ghost": { "type": "http", "url": "http://ghost-mcp:3002/<newblog-token>" },
    "reddit": { "type": "sse", "url": "http://reddit-mcp:8080/sse" },
    "playwright": { "type": "http", "url": "http://playwright-mcp:8931/mcp" },
    "runware": { "type": "http", "url": "http://runware-mcp:8081/mcp" }
  }
}
EOF

# Start runner
docker compose up -d runner-newblog

# Authenticate Claude
docker exec -it runner-newblog claude

Step 9: Caddy

Add to /etc/caddy/Caddyfile:

blog.newblog.camlab.dev {
    reverse_proxy localhost:2371
}

Reload: sudo systemctl reload caddy

Step 10: n8n Workflow

  1. In n8n, duplicate the Cam4 Writer's Room workflow
  2. Rename to "NewBlog Writer's Room"
  3. Update all Claude Code nodes:
  4. Runner container: runner-newblog
  5. Ghost MCP token in any direct references
  6. Update schedule trigger (pick a non-overlapping time slot)
  7. Update Researcher agent with new subreddits
  8. Test with Manual Trigger
  9. Activate

Step 11: Update Documentation

  • [ ] Add to docs/containers.md
  • [ ] Add to docs/networking.md
  • [ ] Add to docs/ghost.md
  • [ ] Log all actions in logs/changes.md

Verification Checklist

[ ] Blog accessible at public URL
[ ] Ghost Admin working at /ghost
[ ] Theme active and rendering
[ ] Ghost MCP responds for new token
[ ] Runner authenticated and responsive
[ ] n8n workflow runs successfully (manual trigger)
[ ] Post published to correct blog
[ ] Caddy TLS certificate valid