---
name: monitor-server-via-portainer-api
category: devops
description: Get server resource metrics (CPU, RAM, disk, containers) from a restricted container using Portainer's REST API with Python stdlib only (no curl, ssh, or pip needed).
tags:
  - portainer
  - docker
  - monitoring
  - restricted-environment
  - python
---

# Monitor Server via Portainer API

Use when you need to check server metrics (CPU, RAM, disk, container
stats, running containers) but you're inside a **restricted container**
with **no SSH, no curl, no pip** — only Python stdlib available.

## Prerequisites

- Portainer web UI is running on the server (port 9000 by default)
- You have Portainer admin credentials (username/password)
- Python 3 is available in your container (stdlib only — `urllib.request`, `json`)

## Steps

### 1️⃣ Login and get JWT token

```python
import urllib.request, json

data = json.dumps({'Username': 'admin', 'Password': 'PASSWORD_HERE'}).encode()
req = urllib.request.Request('http://SERVER_IP:9000/api/auth', data=data,
    headers={'Content-Type': 'application/json'})
resp = urllib.request.urlopen(req, timeout=10)
token = json.loads(resp.read())['jwt']
headers = {'Authorization': f'Bearer {token}'}
```

### 2️⃣ Get system info (CPU, RAM, OS)

```python
req = urllib.request.Request('http://SERVER_IP:9000/api/endpoints/ENDPOINT_ID/docker/info',
    headers=headers)
resp = urllib.request.urlopen(req, timeout=10)
info = json.loads(resp.read())

print('CPU:', info.get('NCPU'))
print('RAM:', info.get('MemTotal'), 'bytes')
print('OS:', info.get('OperatingSystem'))
print('Docker:', info.get('ServerVersion'))
print('Containers Running:', info.get('ContainersRunning'))
```

**Endpoint ID** is usually the local environment ID. Check from
Portainer UI home page URL (`#!/3/docker/...`) — the number is the
endpoint ID (commonly `3` for local).

### 3️⃣ List all containers with their status

```python
req = urllib.request.Request(
    'http://SERVER_IP:9000/api/endpoints/ENDPOINT_ID/docker/containers/json?all=true',
    headers=headers)
resp = urllib.request.urlopen(req, timeout=10)
containers = json.loads(resp.read())

for c in containers:
    name = c.get('Names', ['?'])[0].lstrip('/')
    status = c.get('Status', '?')
    image = c.get('Image', '?')
    print(f'{name}: {status} | {image}')
```

### 4️⃣ Get per-container RAM/CPU stats

```python
for c in containers:
    cid = c['Id'][:12]
    name = c.get('Names', ['?'])[0].lstrip('/')
    try:
        req2 = urllib.request.Request(
            f'http://SERVER_IP:9000/api/endpoints/ENDPOINT_ID/docker/containers/{cid}/stats?stream=false',
            headers=headers)
        resp2 = urllib.request.urlopen(req2, timeout=10)
        stats = json.loads(resp2.read())
        mem_mb = stats['memory_stats']['usage'] / 1024 / 1024
        print(f'{name}: {mem_mb:.1f} MB')
    except Exception as e:
        print(f'{name}: ERROR {e}')
```

### 5️⃣ Check Docker disk usage

```python
req = urllib.request.Request(
    'http://SERVER_IP:9000/api/endpoints/ENDPOINT_ID/docker/system/df',
    headers=headers)
resp = urllib.request.urlopen(req, timeout=10)
df = json.loads(resp.read())
layers = df.get('LayersSize', 0)
print(f'Docker disk: {layers:,} bytes ({layers/1024/1024/1024:.2f} GB)')
```

## ⚠️ Known pitfalls

- **Endpoint ID varies** — check the Portainer URL (`#!/3/docker/...`).
  The number after `#!/` is the endpoint. Typically `3` for the local
  environment on a fresh Portainer setup, but may differ.
- **Stats endpoint returns 404** for stopped containers — skip them or
  filter with `?all=true` parameter.
- **No session persistence** — each Python invocation must re-login.
  Keep the script in one `python3 -c "..."` call.
- **Portainer API version** — these endpoints are for Portainer CE 2.x.
  Future API versions may use different paths.
- **JWT token expires** — Portainer tokens last ~8 hours. For recurring
  checks, re-login each time.

## Verification

After running step 2, confirm you see `NCPU` (number of cores) and
`MemTotal` (total RAM in bytes). If you get `403 Forbidden`, the
endpoint ID is likely wrong.
