A step-by-step infrastructure guide for Claude Code AI marketing agent on Ubuntu 24.04 LTS
Prerequisites
- An AWS account with billing enabled
- A registered domain name (needed for Caddy HTTPS)
- A Google account that owns or has admin access to your GA4 property and Google Ads account
- An Anthropic account for API access
- A local machine with an SSH client (macOS/Linux terminal, or Windows with WSL or PuTTY)
This guide walks you through standing up the complete server environment described in the Claude Code AI Marketing Agent on Ubuntu Pipeline architecture: an AWS EC2 t3.micro running Ubuntu 24.04 LTS, hardened with SSH and a firewall, serving a Node.js pipeline managed by PM2, fronted by Caddy for HTTPS, and connected to four external APIs: Google Analytics 4, Google Ads, Gmail, and Anthropic Claude.
What this guide does not cover is your business logic. The pipeline modes (daily digest, query, content gap) are yours to build once the environment is ready. This guide ensures the environment is solid, the credentials are wired in correctly, and the plumbing works before you write a single line of domain-specific code.
Part 1 – Provision the VPS on AWS
1.1 Launch an EC2 t3.micro Instance
Log in to the AWS Management Console and navigate to EC2. Click Launch Instance and configure it as follows:
- Name: give it something meaningful, e.g., marketing-agent
- AMI: Ubuntu Server 24.04 LTS (HVM), SSD Volume Type – make sure you select 64-bit (x86)
- Instance type: t3.micro (2 vCPU, 1 GB RAM on free tier; the pipeline described uses t3.micro at 4 GB – select t3.small if you want the exact match)
- Key pair: Create a new RSA key pair, download the .pem file, and store it somewhere safe. You will not be able to download it again.
- Storage: 8 GB GP3 is sufficient. The pipeline stores nothing locally.
Under Network settings, create a new security group with the following inbound rules:
Type Port Source
SSH 22 Your IP address only (not 0.0.0.0/0)
HTTP 80 0.0.0.0/0
HTTPS 443 0.0.0.0/0Note: Restricting SSH to your own IP is critical. Never open port 22 to the world.
Click Launch Instance. Wait about 60 seconds for the instance to reach the running state, then note its Public IPv4 address from the instance detail panel.
1.2 Assign an Elastic IP
A regular EC2 public IP changes every time you stop and restart the instance. Assign an Elastic IP so your DNS records stay stable.
In the EC2 console, go to Elastic IPs under Network & Security. Click Allocate Elastic IP address, accept defaults, then click Allocate. Select the new IP, choose Actions > Associate Elastic IP address, and associate it with your instance. Your instance now has a permanent public IP.
1.3 Point Your Domain at the Instance
In your domain registrar or DNS provider, create an A record:
Host: agent.yourdomain.com (or @ for root)
Type: A
Value: <your Elastic IP>
TTL: 300DNS propagation usually takes a few minutes, but can take up to an hour. You can verify it with:
dig agent.yourdomain.com +shortPart 2 – Initial Server Configuration
2.1 Connect via SSH
From your local terminal, set the correct permissions on your key file and connect:
chmod 400 ~/Downloads/your-key.pem
ssh -i ~/Downloads/your-key.pem ubuntu@<your-elastic-ip>You are now inside the server as the Ubuntu user.
2.2 Update the System
sudo apt update && sudo apt upgrade -y
sudo apt autoremove -y2.3 Create a Dedicated Non-Root User
Running applications as root is dangerous. Create a dedicated user for the pipeline:
sudo adduser agent
sudo usermod -aG sudo agentCopy your SSH public key to the new user so you can log in directly as an agent:
sudo mkdir -p /home/agent/.ssh
sudo cp ~/.ssh/authorized_keys /home/agent/.ssh/
sudo chown -R agent:agent /home/agent/.ssh
sudo chmod 700 /home/agent/.ssh
sudo chmod 600 /home/agent/.ssh/authorized_keysOpen a new terminal tab and confirm you can log in as an agent before closing your root session:
ssh -i ~/Downloads/your-key.pem agent@<your-elastic-ip>2.4 Harden SSH
Edit the SSH daemon config:
sudo nano /etc/ssh/sshd_configFind and set these values (add them if they do not exist):
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
Restart SSH to apply:
sudo systemctl restart ssh2.5 Configure UFW Firewall
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
Restart SSH to apply:
sudo systemctl restart sshThe firewall is now active. Only SSH, HTTP, and HTTPS are open.
2.6 Set the Server Timezone
Choose the timezone that matches your business data:
sudo timedatectl set-timezone Europe/Berlin # replace with your zone
timedatectlPart 3 – Install the Node.js Runtime and PM2
3.1 Install Node.js 22 LTS via NodeSource
Ubuntu 24.04 ships with an older Node version. Use the NodeSource repository for the current LTS:
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs
node -v # should print v22.x.x
npm -v3.2 Install PM2 Globally
PM2 keeps your Node.js process alive, restarts it on crash, and manages logs.
sudo npm install -g pm2
pm2 -vConfigure PM2 to start automatically on server reboot:
pm2 startup systemd -u agent --hp /home/agent# PM2 will print a sudo command — run it exactly as printed
3.3 Create the Project Directory
mkdir -p /home/agent/marketing-agent
cd /home/agent/marketing-agent
npm init -yPart 4 – Install and Configure Caddy for HTTPS
Caddy is a modern web server that handles TLS certificates automatically via Let’s Encrypt. Even if your pipeline dispatches by email rather than serving web requests, HTTPS is important if you add any webhook endpoints or admin interfaces later. It also demonstrates a production-ready setup.
4.1 Install Caddy
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy4.2 Configure the Caddyfile
sudo nano /etc/caddy/CaddyfileReplace the default content with:
agent.yourdomain.com {
reverse_proxy localhost:3000
}This tells Caddy to obtain a TLS certificate for your domain automatically and proxy traffic to your Node.js app on port 3000. Reload Caddy:
sudo systemctl reload caddy
sudo systemctl status caddyNote: Caddy will provision the certificate from Let’s Encrypt on the first request. Make sure your domain’s DNS is already pointing to the server before this step.
Part 5 – Anthropic API Credentials
5.1 Get Your API Key
Your Node.js pipeline calls the Anthropic API directly, so you need an API key.
- Go to https://console.anthropic.com
- Sign in or create an account
- Navigate to Settings > API Keys
- Click Create Key, give it a name (e.g., marketing-agent), and copy the value
— You will only see it once
Note: Treat this key like a password. It is billed per token. Never commit it to a git repository.
On the server, store it in your environment file (we will set this up in Part 9):
ANTHROPIC_API_KEY=sk-ant-api03-...Part 6 – Google Cloud Project and OAuth2 Setup
Google Analytics 4, Google Ads, and Gmail all require credentials from a Google Cloud project. You will create one project and enable the three APIs within it.
6.1 Create a Google Cloud Project
- Go to https://console.cloud.google.com
- Click the project dropdown at the top left > New Project
- Name it marketing-agent (or similar)
- Click Create and wait for it to provision
- Select the new project from the dropdown
6.2 Configure the OAuth2 Consent Screen
Before creating credentials, you must configure what the consent screen shows users when they authorise your app.
- In the left sidebar, go to APIs & Services > OAuth consent screen
- Choose User Type: Internal (if using Google Workspace) or External (if using a personal Google account)
- Fill in the App name, User support email, and Developer contact email
- Skip Scopes for now – click Save and Continue through to the end
6.3 Create OAuth2 Credentials
- Go to APIs & Services > Credentials
- Click Create Credentials > OAuth client ID
- Application type: Desktop app
- Name it marketing-agent-local
- Click Create
- Click Download JSON — this saves your client_secret.json
- Copy this file to your server at:
/home/agent/marketing-agent/credentials/google_client_secret.json
Note: You will use this file to generate a token.json via a one-time OAuth flow. Never commit client_secret.json to version control.
Part 7 – Google Analytics 4 API
7.1 Enable the GA4 Data API
- In Google Cloud Console, go to APIs & Services > Library
- Search for Google Analytics Data API
- Click it and press Enable
7.2 Find Your GA4 Property ID
- Go to https://analytics.google.com
- Select your property from the top left
- Go to Admin (gear icon, bottom left)
- Under the Property column, click Property Settings
- Your Property ID is the number shown at the top (e.g., 123456789)
– it is NOT the UA-XXXXXXXXX tracking ID
Add to your environment file:
GA4_PROPERTY_ID=1234567897.3 Grant API Access to Your Google Account
- In GA4 Admin, go to Property Access Management
- Add the Google account you used to create the Cloud project
- Assign the Viewer role (minimum required for read access)
Part 8 – Google Ads API
8.1 Enable the Google Ads API
- In Google Cloud Console, go to APIs & Services > Library
- Search for Google Ads API
- Click Enable
8.2 Apply for Google Ads API Access
Google Ads API requires a separate access application for production use.
- Go to https://ads.google.com/aw/apicenter
- Sign in with the Google account that manages your Ads account
- Fill in the API access application form
- For a basic read/reporting setup, access is usually approved quickly
8.3 Get Your Developer Token
- In Google Ads, click Tools & Settings (wrench icon, top right)
- Under Setup, click API Center
- Your Developer Token is shown here
– initially in Test Account mode; apply for Basic Access for live data
Add to your environment file:
GOOGLE_ADS_DEVELOPER_TOKEN=your-developer-token
GOOGLE_ADS_CUSTOMER_ID=111-222-3333 # your Ads account ID without dashes: 11122233338.4 Get Your Ads Login Customer ID
If you manage ads through an MCC (manager account), you also need the login customer ID:
GOOGLE_ADS_LOGIN_CUSTOMER_ID=999-888-7777 # your MCC account ID, if applicablePart 9 – Gmail API
9.1 Enable the Gmail API
- In Google Cloud Console, go to APIs & Services > Library
- Search for Gmail API
- Click Enable
9.2 Add Gmail Scopes to the OAuth Consent Screen
- Go to APIs & Services > OAuth consent screen
- Click Edit App
- On the Scopes step, click Add or Remove Scopes
- Add the following scope:
https://www.googleapis.com/auth/gmail.send - Save and continue
Note: If your app is External and not yet verified by Google, you can add test users under the OAuth consent screen. Add your own email address as a test user.
Part 10 – Google Drive API (for Content Output)
10.1 Enable the Google Drive API
- In Google Cloud Console, go to APIs & Services > Library
- Search for Google Drive API
- Click Enable
10.2 Add Drive Scopes
- Go to OAuth consent screen > Edit App > Scopes
- Add:
https://www.googleapis.com/auth/drive.file
– this allows creating and managing files your app creates) - Save and continue
Part 11 – Generate the Google OAuth2 Token
All Google APIs (GA4, Ads, Gmail, Drive) share one OAuth2 token generated from your client_secret.json. You run this once locally to produce a token.json that you then copy to the server.
11.1 Install the Google Auth Library Locally
On your local machine (not the server):
npm install googleapis google-auth-library11.2 Run the OAuth2 Flow
Create a file called auth.js with this content:
const { google } = require('googleapis');
const { authenticate } = require('@google-cloud/local-auth');
const path = require('path');
const fs = require('fs');
const SCOPES = [
'https://www.googleapis.com/auth/analytics.readonly',
'https://www.googleapis.com/auth/adwords',
'https://www.googleapis.com/auth/gmail.send',
'https://www.googleapis.com/auth/drive.file',
];
async function main() {
const client = await authenticate({
scopes: SCOPES,
keyfilePath: path.join(__dirname, 'credentials/google_client_secret.json'),
});
const tokens = client.credentials;
fs.writeFileSync('./credentials/token.json', JSON.stringify(tokens, null, 2));
console.log('token.json saved');
}
main().catch(console.error);Run it:
node auth.jsA browser window opens. Sign in with the Google account that has access to all four services. Grant all requested permissions. The script saves token.json.
Copy the token to your server:
scp -i ~/Downloads/your-key.pem ./credentials/token.json \
agent@<your-elastic-ip>:/home/agent/marketing-agent/credentials/token.jsonPart 12 – Environment File and Project Structure
12.1 Create the .env File
On the server, inside your project directory:
cd /home/agent/marketing-agent
nano .envPaste in all credentials collected in the previous sections:
# Anthropic
ANTHROPIC_API_KEY=sk-ant-api03-...
# Google OAuth2
GOOGLE_CLIENT_SECRET_PATH=./credentials/google_client_secret.json
GOOGLE_TOKEN_PATH=./credentials/token.json
# Google Analytics 4
GA4_PROPERTY_ID=123456789
# Google Ads
GOOGLE_ADS_DEVELOPER_TOKEN=your-developer-token
GOOGLE_ADS_CUSTOMER_ID=1112223333
GOOGLE_ADS_LOGIN_CUSTOMER_ID=9998887777
# Gmail
[email protected]
[email protected]
# General
PORT=3000
TZ=Europe/BerlinRestrict permissions so only the agent user can read it:
chmod 600 .env12.2 Recommended Project Structure
marketing-agent/
├── .env
├── CLAUDE.md
├── package.json
├── credentials/
│ ├── google_client_secret.json
│ └── token.json
├── src/
│ ├── index.js # entry point
│ ├── modes/
│ │ ├── daily-digest.js
│ │ ├── query.js
│ │ └── content-gap.js
│ └── connectors/
│ ├── ga4.js
│ ├── google-ads.js
│ ├── gmail.js
│ └── drive.js
└── logs/12.3 The CLAUDE.md File
CLAUDE.md is a plain text file in the project root that gets read automatically by Claude Code at the start of every session. It is where you define the agent’s identity, knowledge, guardrails, and decision rules. Think of it as the agent’s permanent memory and operating manual.
Create it on the server:
nano /home/agent/marketing-agent/CLAUDE.mdThe structure of this file is entirely up to you and your business context. At minimum, it should cover:
- Who the agent is and what company it represents
- What data sources it has access to and what each one contains
- The brand voice, tone guidelines, and style rules for generated content
- Guardrails: what the agent is never allowed to do autonomously (e.g. change budgets by more than X%, delete anything, create campaigns without approval)
- How decisions should be escalated to a human
- The ICP (ideal customer profile), sales goals, and any active campaign context
Note: CLAUDE.md is what turns a generic API call into a system that has context about your business. The more precise it is, the less you need to repeat in your prompt templates.
Part 13 – Install Project Dependencies
On the server, install the Node.js libraries your pipeline will use:
cd /home/agent/marketing-agent
npm install @anthropic-ai/sdk googleapis google-auth-library \
dotenv node-cron axios cheerioA brief explanation of each:
- @anthropic-ai/sdk — official Anthropic Node.js SDK for Claude API calls
- googleapis — Google’s official Node.js client library for GA4, Ads, Gmail, and Drive
- google-auth-library — handles OAuth2 token refresh automatically
- dotenv — loads your .env file into process.env at startup
- node-cron — schedules the daily digest and other recurring jobs
- axios — HTTP client for fetching your sitemap and external URLs
- cheerio — HTML parser for crawling your site’s content
Part 14 – Install Claude Code on the Server
Claude Code is Anthropic’s CLI agent that can read your project files, understand your codebase, and execute tasks in the terminal. When you start a Claude Code session inside your project directory, it reads CLAUDE.md automatically, giving the agent immediate context about your business.
14.1 Install Claude Code
npm install -g @anthropic-ai/claude-code
claude --version14.2 Authenticate Claude Code
claude auth login
# Follow the prompt to enter your Anthropic API key14.3 Start a Session in Your Project
cd /home/agent/marketing-agent
claudeClaude Code will read CLAUDE.md automatically and be ready to help you build the pipeline logic, debug connectors, and iterate on your modes of operation. Use it interactively during development, and rely on the Node.js pipeline (managed by PM2) for scheduled autonomous runs.
Part 15 – Register the Pipeline with PM2
Once you have written your entry point (src/index.js), register it with PM2 so it runs continuously and restarts on failure.
15.1 Start the Process
cd /home/agent/marketing-agent
pm2 start src/index.js --name marketing-agent --env production
pm2 save # persist the process list across reboots15.2 Useful PM2 Commands
pm2 status # see all running processes
pm2 logs marketing-agent # tail live logs
pm2 logs marketing-agent --lines 200 # last 200 lines
pm2 restart marketing-agent # restart after a code change
pm2 stop marketing-agent # stop without removing
pm2 delete marketing-agent # remove from PM2 entirely15.3 Configure Log Rotation
Prevent log files from growing unbounded:
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7Part 16 – Verify the Full Stack
Run through this checklist to confirm every layer is working before you start building business logic.
- SSH access works as the agent user with key-based auth only
- UFW firewall is active and shows only ports 22, 80, 443 as allowed
- Node.js v22 and PM2 are installed and PM2 startup is configured
- Caddy is running and your domain resolves over HTTPS (test in a browser)
- Your .env file is present and readable only by the agent user (chmod 600)
- credentials/google_client_secret.json and credentials/token.json are in place
- ANTHROPIC_API_KEY works: test with a minimal SDK call from the project directory
- GA4 API works: write a two-line script that fetches sessions for the last 7 days and prints the result
- Gmail API works: send a test email to yourself
- Google Drive API works: create a test document and confirm it appears in Drive
- Claude Code starts in the project directory and reads CLAUDE.md correctly
- PM2 shows your process as online after pm2 status
Note: Test each API in isolation before combining them. Debugging one connection at a time is dramatically faster than debugging a combined pipeline.
Part 17 – What Comes Next
The environment is ready. The plumbing is in place. What happens inside the pipeline is yours to define.
You have a server that knows how to talk to your data, relay the analysis to Claude, and dispatch outputs via email, terminal, or Drive. The CLAUDE.md file is the control plane for the agent’s identity and guardrails. PM2 keeps everything alive.
The three operating modes described in the architecture (daily digest, query, content gap) are starting points. Each one is a different way of asking the same question: given what we know, what should we do next? The answer to that question belongs to your business, not this guide.
When you are ready to expand, the pattern is repeatable: a new directory, a new set of credentials, a new CLAUDE.md, a new PM2 process. One VPS, clean separation, shared infrastructure.
Appendix – Quick Reference
Environment Variables Summary
ANTHROPIC_API_KEY
GOOGLE_CLIENT_SECRET_PATH
GOOGLE_TOKEN_PATH
GA4_PROPERTY_ID
GOOGLE_ADS_DEVELOPER_TOKEN
GOOGLE_ADS_CUSTOMER_ID
GOOGLE_ADS_LOGIN_CUSTOMER_ID
GMAIL_SENDER
GMAIL_RECIPIENT
PORT
TZKey URLs
- Anthropic Console: https://console.anthropic.com
- Google Cloud Console: https://console.cloud.google.com
- Google Analytics: https://analytics.google.com
- Google Ads API Center: https://ads.google.com/aw/apicenter
- Caddy docs: https://caddyserver.com/docs
- PM2 docs: https://pm2.keymetrics.io/docs
- Claude Code docs: https://docs.anthropic.com/claude-code
OAuth2 Scopes Used