feat: change default listen port from 3000 to 1852 (#756)

Updates the backend listen port, Vite dev proxy target, Docker EXPOSE,
compose port mapping, .env.example default, GitHub Actions smoke-test
default, healthcheck URLs, and every doc/example reference. Test fixtures
that include example URLs were updated for consistency, though their
assertions are port-agnostic.

The rate-limit value of 3000 in middleware/rateLimiters.ts and the
3000 entry in WEB_UI_PORTS (which detects user containers like Grafana)
are intentionally untouched.
This commit is contained in:
Anso
2026-04-24 22:23:31 -04:00
committed by GitHub
parent d6b744e8e6
commit ed553f1f19
27 changed files with 72 additions and 72 deletions
+2 -2
View File
@@ -7,8 +7,8 @@ JWT_SECRET=your-secure-jwt-secret-here
# Directory containing docker-compose files
COMPOSE_DIR=/path/to/your/compose/files
# HTTP server port (default: 3000)
PORT=3000
# HTTP server port (default: 1852)
PORT=1852
# Database and state directory inside the container (default: /app/data)
DATA_DIR=/app/data
+1 -1
View File
@@ -13,7 +13,7 @@ inputs:
default: '/tmp/compose'
port:
required: false
default: '3000'
default: '1852'
skip-backend-build:
description: >
When 'true', skip running `npm run build` in backend/ and assume the
+3 -3
View File
@@ -126,12 +126,12 @@ jobs:
# enough for `docker logs` in the trap to surface the stack trace.
# The trap force-removes it explicitly whether it is still running
# or already exited.
docker run -d --name sencho-smoke -p 3000:3000 localhost/sencho:release-scan
docker run -d --name sencho-smoke -p 1852:1852 localhost/sencho:release-scan
trap 'docker logs sencho-smoke 2>&1 || true; docker rm -f sencho-smoke >/dev/null 2>&1 || true' EXIT
for i in $(seq 1 30); do
if curl -fsS http://localhost:3000/api/health >/dev/null 2>&1; then
if curl -fsS http://localhost:1852/api/health >/dev/null 2>&1; then
echo "Container healthy after ${i}s"
curl -s http://localhost:3000/api/health
curl -s http://localhost:1852/api/health
exit 0
fi
sleep 1
+1 -1
View File
@@ -14,7 +14,7 @@ Thank you for your interest in contributing to Sencho!
```
5. Start the dev servers:
```bash
cd backend && npm run dev # Express + nodemon on :3000
cd backend && npm run dev # Express + nodemon on :1852
cd frontend && npm run dev # Vite on :5173
```
+2 -2
View File
@@ -184,11 +184,11 @@ RUN sed -i 's/\r//' /usr/local/bin/docker-entrypoint.sh \
&& chmod +x /usr/local/bin/docker-entrypoint.sh
# Expose port
EXPOSE 3000
EXPOSE 1852
# Health check - polls the public /api/health endpoint every 30s
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD node -e "const h=require('http');h.get('http://localhost:3000/api/health',r=>{process.exit(r.statusCode===200?0:1)}).on('error',()=>process.exit(1))"
CMD node -e "const h=require('http');h.get('http://localhost:1852/api/health',r=>{process.exit(r.statusCode===200?0:1)}).on('error',()=>process.exit(1))"
# Entrypoint ensures /app/data is writable and execs the CMD as root by default,
# or drops to $SENCHO_USER via su-exec when that env var is set (see comment above).
+3 -3
View File
@@ -28,7 +28,7 @@ services:
container_name: sencho
restart: unless-stopped
ports:
- "3000:3000"
- "1852:1852"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./data:/app/data
@@ -43,7 +43,7 @@ services:
docker compose up -d
```
Then open `http://your-server:3000` and create your admin account.
Then open `http://your-server:1852` and create your admin account.
See the [full documentation](https://docs.sencho.io) for configuration details, multi-node setup, and more.
@@ -57,7 +57,7 @@ cd backend && npm install && npm run dev
cd frontend && npm install && npm run dev
```
The frontend dev server proxies `/api` requests to the backend on port 3000.
The frontend dev server proxies `/api` requests to the backend on port 1852.
## Contributing
+1 -1
View File
@@ -63,7 +63,7 @@ describe('POST /api/nodes - api_url SSRF validation (C2 fix)', () => {
.send({
name: 'lan-node',
type: 'remote',
api_url: 'http://192.168.1.50:3000',
api_url: 'http://192.168.1.50:1852',
api_token: 'sometoken',
});
// Should succeed (201 or 200) - not a validation error
@@ -1185,7 +1185,7 @@ describe('SchedulerService - executeUpdateRemote', () => {
it('proxies update execution to remote node', async () => {
mockGetNode.mockReturnValue({ id: 2, name: 'remote', type: 'remote', status: 'online' });
mockGetProxyTarget.mockReturnValue({
apiUrl: 'http://remote:3000',
apiUrl: 'http://remote:1852',
apiToken: 'test-token',
});
@@ -1211,7 +1211,7 @@ describe('SchedulerService - executeUpdateRemote', () => {
await svc.triggerTask(88);
expect(mockFetch).toHaveBeenCalledWith(
'http://remote:3000/api/auto-update/execute',
'http://remote:1852/api/auto-update/execute',
expect.objectContaining({
method: 'POST',
body: JSON.stringify({ target: 'web-app' }),
@@ -1226,7 +1226,7 @@ describe('SchedulerService - executeUpdateRemote', () => {
it('records failure when remote node returns error', async () => {
mockGetNode.mockReturnValue({ id: 2, name: 'remote', type: 'remote', status: 'online' });
mockGetProxyTarget.mockReturnValue({
apiUrl: 'http://remote:3000',
apiUrl: 'http://remote:1852',
apiToken: 'test-token',
});
@@ -210,11 +210,11 @@ describe('TemplateService', () => {
it('handles values with special characters', () => {
const result = service.generateEnvString({
PASSWORD: 'p@ss=word!',
URL: 'http://localhost:3000',
URL: 'http://localhost:1852',
});
expect(result).toContain('PASSWORD=p@ss=word!');
expect(result).toContain('URL=http://localhost:3000');
expect(result).toContain('URL=http://localhost:1852');
});
});
+1 -1
View File
@@ -568,7 +568,7 @@ describe('Orphaned role assignment cleanup', () => {
it('deleting a node removes its role assignments', async () => {
const db = DatabaseService.getInstance();
// Create a test node
const nodeId = db.addNode({ name: 'test-cleanup-node', type: 'remote', api_url: 'http://test:3000', api_token: '', compose_dir: '/tmp', is_default: false });
const nodeId = db.addNode({ name: 'test-cleanup-node', type: 'remote', api_url: 'http://test:1852', api_token: '', compose_dir: '/tmp', is_default: false });
// Create a role assignment for this node
const hash = await bcrypt.hash('password123', 1);
const userId = db.addUser({ username: 'nodeorphan', password_hash: hash, role: 'viewer' });
+8 -8
View File
@@ -63,7 +63,7 @@ describe('isValidStackName', () => {
describe('isValidRemoteUrl', () => {
it('accepts valid http URLs', () => {
const result = isValidRemoteUrl('http://192.168.1.10:3000');
const result = isValidRemoteUrl('http://192.168.1.10:1852');
expect(result.valid).toBe(true);
});
@@ -84,25 +84,25 @@ describe('isValidRemoteUrl', () => {
});
it('rejects localhost', () => {
expect(isValidRemoteUrl('http://localhost:3000').valid).toBe(false);
expect(isValidRemoteUrl('http://LOCALHOST:3000').valid).toBe(false);
expect(isValidRemoteUrl('http://localhost:1852').valid).toBe(false);
expect(isValidRemoteUrl('http://LOCALHOST:1852').valid).toBe(false);
});
it('rejects loopback IPs', () => {
expect(isValidRemoteUrl('http://127.0.0.1:3000').valid).toBe(false);
expect(isValidRemoteUrl('http://127.0.0.1:1852').valid).toBe(false);
expect(isValidRemoteUrl('http://127.1.2.3').valid).toBe(false);
// Node.js URL.hostname preserves brackets: new URL('http://[::1]').hostname === '[::1]'
expect(isValidRemoteUrl('http://[::1]:3000').valid).toBe(false);
expect(isValidRemoteUrl('http://[::1]:1852').valid).toBe(false);
});
it('rejects 0.0.0.0', () => {
expect(isValidRemoteUrl('http://0.0.0.0:3000').valid).toBe(false);
expect(isValidRemoteUrl('http://0.0.0.0:1852').valid).toBe(false);
});
it('allows LAN/private IPs (users need these for local network nodes)', () => {
// Users legitimately run Sencho nodes on their LAN
expect(isValidRemoteUrl('http://192.168.1.100:3000').valid).toBe(true);
expect(isValidRemoteUrl('http://10.0.0.5:3000').valid).toBe(true);
expect(isValidRemoteUrl('http://192.168.1.100:1852').valid).toBe(true);
expect(isValidRemoteUrl('http://10.0.0.5:1852').valid).toBe(true);
});
});
+1 -1
View File
@@ -3,7 +3,7 @@
// monolith.
// Server
export const PORT = 3000;
export const PORT = 1852;
// Password policy
export const MIN_PASSWORD_LENGTH = 8;
+1 -1
View File
@@ -37,7 +37,7 @@ function mintPilotEnrollment(nodeId: number, req: Request): { token: string; exp
const forwardedProto = req.headers['x-forwarded-proto'];
const protoHeader = Array.isArray(forwardedProto) ? forwardedProto[0] : forwardedProto;
const protocol = protoHeader || req.protocol || 'http';
const host = req.get('host') || 'localhost:3000';
const host = req.get('host') || 'localhost:1852';
const primaryUrl = `${protocol}://${host}`;
const dockerRun =
+1 -1
View File
@@ -22,7 +22,7 @@ export function isValidRemoteUrl(
console.warn('[Validation] URL parse failure:', (e as Error).message, '— input:', raw);
return {
valid: false,
reason: 'API URL must be a valid URL (e.g. https://my-server.example.com:3000)',
reason: 'API URL must be a valid URL (e.g. https://my-server.example.com:1852)',
};
}
if (!['http:', 'https:'].includes(url.protocol)) {
+1 -1
View File
@@ -5,7 +5,7 @@ services:
container_name: sencho
restart: unless-stopped
ports:
- "3000:3000"
- "1852:1852"
volumes:
# Required: Docker Socket for container orchestration
- /var/run/docker.sock:/var/run/docker.sock
+8 -8
View File
@@ -8,7 +8,7 @@ Sencho exposes a REST API for automating stack deployments, managing webhooks, m
## Base URL
```
https://your-sencho-instance:3000/api
https://your-sencho-instance:1852/api
```
Replace with your actual Sencho host and port. All API paths are prefixed with `/api`.
@@ -19,7 +19,7 @@ Authenticated endpoints require a **Bearer token** in the `Authorization` header
```bash
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
https://your-sencho-instance:3000/api/stacks
https://your-sencho-instance:1852/api/stacks
```
<Note>
@@ -51,7 +51,7 @@ If omitted, the request targets the default node.
# Target node ID 2
curl -H "Authorization: Bearer TOKEN" \
-H "x-node-id: 2" \
https://your-sencho-instance:3000/api/stacks
https://your-sencho-instance:1852/api/stacks
```
## Error format
@@ -132,7 +132,7 @@ Sencho also provides real-time streaming via WebSocket connections. These are no
Stream live logs from a stack's containers.
**URL:** `wss://your-sencho-instance:3000/api/stacks/{stackName}/logs?nodeId={nodeId}`
**URL:** `wss://your-sencho-instance:1852/api/stacks/{stackName}/logs?nodeId={nodeId}`
**Authentication:** Pass the token as a cookie (`sencho_token`) or Bearer token. For WebSocket connections, authentication is verified during the upgrade handshake.
@@ -142,7 +142,7 @@ Stream live logs from a stack's containers.
import WebSocket from "ws";
const ws = new WebSocket(
"wss://your-sencho-instance:3000/api/stacks/my-app/logs",
"wss://your-sencho-instance:1852/api/stacks/my-app/logs",
{ headers: { Cookie: "sencho_token=YOUR_JWT" } }
);
@@ -156,7 +156,7 @@ curl -i -N \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Cookie: sencho_token=YOUR_JWT" \
https://your-sencho-instance:3000/api/stacks/my-app/logs
https://your-sencho-instance:1852/api/stacks/my-app/logs
```
</CodeGroup>
@@ -165,7 +165,7 @@ curl -i -N \
Open an interactive shell session inside a running container.
**URL:** `wss://your-sencho-instance:3000/ws`
**URL:** `wss://your-sencho-instance:1852/ws`
**Authentication:** Cookie-based JWT only. API tokens with `read-only` or `deploy-only` scope are blocked.
@@ -174,7 +174,7 @@ Open an interactive shell session inside a running container.
```javascript Node.js
import WebSocket from "ws";
const ws = new WebSocket("wss://your-sencho-instance:3000/ws", {
const ws = new WebSocket("wss://your-sencho-instance:1852/ws", {
headers: { Cookie: "sencho_token=YOUR_JWT" },
});
+9 -9
View File
@@ -21,7 +21,7 @@ Writes are admin-only and rejected on replica nodes (policies are managed on the
```bash
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
https://your-sencho-instance:3000/api/security/policies
https://your-sencho-instance:1852/api/security/policies
```
**Response:**
@@ -60,7 +60,7 @@ curl -H "Authorization: Bearer YOUR_API_TOKEN" \
| `node_id` | number or `null` | no | Scope the policy to one node. `null` applies the policy fleet-wide. |
```bash
curl -X POST https://your-sencho-instance:3000/api/security/policies \
curl -X POST https://your-sencho-instance:1852/api/security/policies \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
@@ -89,7 +89,7 @@ curl -X POST https://your-sencho-instance:3000/api/security/policies \
Any of the create fields can be updated individually. Omitted fields are left unchanged.
```bash
curl -X PUT https://your-sencho-instance:3000/api/security/policies/1 \
curl -X PUT https://your-sencho-instance:1852/api/security/policies/1 \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "block_on_deploy": 0 }'
@@ -106,7 +106,7 @@ curl -X PUT https://your-sencho-instance:3000/api/security/policies/1 \
**License:** Skipper or Admiral · **Role:** Admin
```bash
curl -X DELETE https://your-sencho-instance:3000/api/security/policies/1 \
curl -X DELETE https://your-sencho-instance:1852/api/security/policies/1 \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
@@ -156,7 +156,7 @@ Response rows include an `active` boolean computed from the `expires_at` timesta
| `expires_at` | number or `null` | no | Unix timestamp in milliseconds. `null` for an indefinite suppression. |
```bash
curl -X POST https://your-sencho-instance:3000/api/security/suppressions \
curl -X POST https://your-sencho-instance:1852/api/security/suppressions \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
@@ -179,7 +179,7 @@ curl -X POST https://your-sencho-instance:3000/api/security/suppressions \
**License:** Skipper or Admiral · **Role:** Admin
```bash
curl -X DELETE https://your-sencho-instance:3000/api/security/suppressions/3 \
curl -X DELETE https://your-sencho-instance:1852/api/security/suppressions/3 \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
@@ -201,7 +201,7 @@ Accepts an image reference and starts an asynchronous scan. The response returns
| `scanners` | `["vuln"]` or `["vuln","secret"]` | no | Omit for vuln-only. `secret` requires Skipper or Admiral. |
```bash
curl -X POST https://your-sencho-instance:3000/api/security/scan \
curl -X POST https://your-sencho-instance:1852/api/security/scan \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "imageRef": "nginx:1.27" }'
@@ -221,7 +221,7 @@ Supports filters: `imageRef`, `imageRefLike`, `status`, `limit`, `offset`. Respo
```bash
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
"https://your-sencho-instance:3000/api/security/scans?imageRefLike=nginx&limit=20"
"https://your-sencho-instance:1852/api/security/scans?imageRefLike=nginx&limit=20"
```
## Stack deploy with policy
@@ -266,7 +266,7 @@ HTTP 409 Conflict. The response body is parseable JSON so CI pipelines can branc
Admins can bypass the gate on a per-deploy basis by passing `?ignorePolicy=true` on the deploy URL. The flag is only honored when the calling session resolves to an admin user; API tokens with read-only or deploy-only scope cannot bypass a policy.
```bash
curl -X POST "https://your-sencho-instance:3000/api/stacks/myapp/deploy?ignorePolicy=true" \
curl -X POST "https://your-sencho-instance:1852/api/stacks/myapp/deploy?ignorePolicy=true" \
-H "Authorization: Bearer ADMIN_API_TOKEN"
```
+6 -6
View File
@@ -39,7 +39,7 @@ On your **primary** Sencho instance, open **Settings → Nodes** and click **+ A
|-------|-------------|
| **Name** | A display name (e.g. `Production VPS`, `media-box`) |
| **Type** | Select **Remote** (or **Local** for an additional local Docker socket) |
| **Sencho API URL** | The full HTTP/HTTPS URL of the remote instance (e.g. `http://192.168.1.50:3000`) |
| **Sencho API URL** | The full HTTP/HTTPS URL of the remote instance (e.g. `http://192.168.1.50:1852`) |
| **API Token** | The token you generated in Step 1 |
| **Compose Directory** | The root directory where compose stack folders live on the remote node (defaults to `/app/compose`) |
@@ -156,8 +156,8 @@ There are three recommended approaches depending on your deployment:
If all your Sencho instances are on the same local network, VPC, or subnet, HTTP is perfectly fine. The token never leaves the private network, so there is no interception risk.
```
http://192.168.1.50:3000 ← safe on a private LAN
http://10.0.1.20:3000 ← safe inside a VPC
http://192.168.1.50:1852 ← safe on a private LAN
http://10.0.1.20:1852 ← safe inside a VPC
```
#### VPN tunnel (WireGuard, Tailscale)
@@ -169,7 +169,7 @@ http://10.0.1.20:3000 ← safe inside a VPC
With a mesh VPN like [Tailscale](https://tailscale.com) or [WireGuard](https://www.wireguard.com/), each server gets a private IP on the VPN. Use those IPs as your Sencho API URLs:
```
http://100.64.0.2:3000 ← Tailscale IP, encrypted by the VPN tunnel
http://100.64.0.2:1852 ← Tailscale IP, encrypted by the VPN tunnel
```
All traffic between nodes is encrypted by the VPN. Sencho does not need to do anything additional.
@@ -181,7 +181,7 @@ If you prefer TLS termination at each node, place a reverse proxy in front of ea
<CodeGroup>
```text Caddyfile
sencho.example.com {
reverse_proxy localhost:3000
reverse_proxy localhost:1852
}
```
@@ -194,7 +194,7 @@ server {
ssl_certificate_key /etc/letsencrypt/live/sencho.example.com/privkey.pem;
location / {
proxy_pass http://localhost:3000;
proxy_pass http://localhost:1852;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
+1 -1
View File
@@ -186,7 +186,7 @@ Only one policy is evaluated per deploy; use a single tight pattern rather than
Policy CRUD endpoints are documented in the [Security API reference](/api-reference/security). A typical create call from CI looks like this:
```bash
curl -X POST https://your-sencho-instance:3000/api/security/policies \
curl -X POST https://your-sencho-instance:1852/api/security/policies \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
+7 -7
View File
@@ -23,7 +23,7 @@ When you point `COMPOSE_DIR` at a directory, Sencho expects each stack to live i
| Variable | Default | Description |
|----------|---------|-------------|
| `PORT` | `3000` | Port the Sencho HTTP server listens on. |
| `PORT` | `1852` | Port the Sencho HTTP server listens on. |
| `DATA_DIR` | `/app/data` | Directory where Sencho stores its SQLite database, node registry, and cached metrics. |
| `FRONTEND_URL` | *(empty)* | Frontend origin for CORS. Only needed if the UI is served from a different domain than the API. Leave empty for same-origin setups. |
| `NODE_ENV` | `production` | Set automatically in the Docker image. Only change this for local development. |
@@ -140,7 +140,7 @@ services:
image: saelix/sencho:latest
restart: unless-stopped
ports:
- "3000:3000"
- "1852:1852"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./sencho-data:/app/data
@@ -180,7 +180,7 @@ server {
server_name sencho.yourdomain.com;
location / {
proxy_pass http://localhost:3000;
proxy_pass http://localhost:1852;
proxy_http_version 1.1;
# WebSocket support
@@ -211,7 +211,7 @@ server {
ssl_certificate_key /etc/letsencrypt/live/sencho.yourdomain.com/privkey.pem;
location / {
proxy_pass http://localhost:3000;
proxy_pass http://localhost:1852;
proxy_http_version 1.1;
# WebSocket support
@@ -235,7 +235,7 @@ Use [Certbot](https://certbot.eff.org/) to obtain and auto-renew certificates: `
labels:
- "traefik.enable=true"
- "traefik.http.routers.sencho.rule=Host(`sencho.yourdomain.com`)"
- "traefik.http.services.sencho.loadbalancer.server.port=3000"
- "traefik.http.services.sencho.loadbalancer.server.port=1852"
```
### Traefik with SSL (Let's Encrypt)
@@ -246,7 +246,7 @@ labels:
- "traefik.http.routers.sencho.rule=Host(`sencho.yourdomain.com`)"
- "traefik.http.routers.sencho.entrypoints=websecure"
- "traefik.http.routers.sencho.tls.certresolver=letsencrypt"
- "traefik.http.services.sencho.loadbalancer.server.port=3000"
- "traefik.http.services.sencho.loadbalancer.server.port=1852"
# HTTP to HTTPS redirect
- "traefik.http.routers.sencho-http.rule=Host(`sencho.yourdomain.com`)"
- "traefik.http.routers.sencho-http.entrypoints=web"
@@ -262,7 +262,7 @@ labels:
```
sencho.yourdomain.com {
reverse_proxy localhost:3000
reverse_proxy localhost:1852
}
```
+2 -2
View File
@@ -13,7 +13,7 @@ description: Get Sencho running in under five minutes.
```bash
docker run -d \
--name sencho \
-p 3000:3000 \
-p 1852:1852 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /opt/compose:/opt/compose \
-v sencho_data:/app/data \
@@ -21,7 +21,7 @@ docker run -d \
saelix/sencho:latest
```
Open `http://localhost:3000` in your browser. On first boot you'll be prompted to create an admin account.
Open `http://localhost:1852` in your browser. On first boot you'll be prompted to create an admin account.
<Note>
Replace `/opt/compose` with the path to your Compose projects directory. Every subdirectory inside it becomes a stack in Sencho. A `JWT_SECRET` is generated automatically on first boot; you do not need to provide one.
+1 -1
View File
@@ -162,7 +162,7 @@ services:
image: saelix/sencho:latest
restart: unless-stopped
ports:
- "3000:3000"
- "1852:1852"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./sencho-data:/app/data
+2 -2
View File
@@ -44,7 +44,7 @@ servers:
host:
default: localhost
port:
default: "3000"
default: "1852"
security:
- bearerAuth: []
@@ -1679,7 +1679,7 @@ paths:
api_url:
type: string
description: Remote Sencho instance URL (required for remote nodes).
example: https://sencho.example.com:3000
example: https://sencho.example.com:1852
api_token:
type: string
description: API token for authenticating with the remote Sencho instance.
+1 -1
View File
@@ -70,7 +70,7 @@ Sencho itself is lightweight. The majority of resource usage on your host comes
## Networking
- **Listen port:** 3000 (fixed). Map it to any host port using Docker's `-p` flag or `ports` in your compose file
- **Listen port:** 1852 (fixed). Map it to any host port using Docker's `-p` flag or `ports` in your compose file
- **Inbound:** Only the listen port needs to be reachable (directly or through a reverse proxy)
- **Outbound:** No outbound connections are required for local-only setups. If you use multi-node management, Sencho needs HTTP/HTTPS access to remote Sencho instances on their configured API URLs
- **Health check:** `GET /api/health` returns `200` when the application is ready. The Docker image includes a built-in `HEALTHCHECK` that polls this endpoint every 30 seconds
+1 -1
View File
@@ -557,7 +557,7 @@ docker compose pull && docker compose up -d
Sencho exposes a health endpoint for monitoring and container health checks:
```bash
curl http://localhost:3000/api/health
curl http://localhost:1852/api/health
# {"status":"ok","uptime":12345.67}
```
+1 -1
View File
@@ -368,7 +368,7 @@ export function NodeManager() {
<Label htmlFor="node-api-url">Sencho API URL</Label>
<Input
id="node-api-url"
placeholder="http://192.168.1.50:3000"
placeholder="http://192.168.1.50:1852"
value={formData.api_url}
onChange={(e) => setFormData({ ...formData, api_url: e.target.value })}
/>
+2 -2
View File
@@ -27,12 +27,12 @@ export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
target: 'http://localhost:1852',
changeOrigin: true,
ws: true,
},
'/ws': {
target: 'http://localhost:3000',
target: 'http://localhost:1852',
changeOrigin: true,
ws: true,
},