{"service":"fullstacked-cloud-docs","version":"1.22.3","environment":"live","generatedAt":"2026-10-24T09:38:39.690Z","formats":{"html":"https://docs.fullstacked.cloud/","markdown":"https://docs.fullstacked.cloud/?format=md","json":"https://docs.fullstacked.cloud/?format=json"},"services":{"console":"https://console.fullstacked.cloud","auth":"https://auth.fullstacked.cloud","git":"https://git.fullstacked.cloud","tunnels":"https://tunnels.fullstacked.cloud","tunnelsHost":"tunnels.fullstacked.cloud","mcp":"https://mcp.fullstacked.cloud","install":"https://install.fullstacked.cloud","docs":"https://docs.fullstacked.cloud"},"runtime":{"version":"1.0.0-alpha.1844","guide":"https://docs.fullstacked.cloud/runtime?format=md"},"dataEngineLimits":{"postgres":{"image":"postgres:18.6-alpine","port":5432,"memoryMB":256,"cpus":2,"pids":256},"redis":{"image":"redis:8.10.2-alpine","port":6379,"memoryMB":256,"cpus":1,"pids":128},"mongodb":{"image":"mongo:8.3.11-noble","port":27017,"memoryMB":512,"cpus":2,"pids":512},"s3":{"image":"rustfs/rustfs:1.0.0","port":9000,"memoryMB":768,"cpus":2,"pids":1024}},"projectRuntimeEnv":[{"name":"TUNNELS_HOST","injectedBy":"GET /api/v1/projects/:id/env","description":"Tunnels hub host for tunnel.register from fullstacked/tunnel."},{"name":"AUTH_HOST","injectedBy":"GET /api/v1/projects/:id/env","description":"Bare host of the Authentication service."},{"name":"AUTH_URL","injectedBy":"GET /api/v1/projects/:id/env","description":"Base URL of the Authentication service (GET ${AUTH_URL}/api/v1/me)."},{"name":"DOCS_URL","injectedBy":"GET /api/v1/projects/:id/env","description":"Base URL of this documentation (?format=md | ?format=json)."},{"name":"AUTH_TOKEN","injectedBy":"Client GUI / action runner at launch (in memory, never stored)","description":"The signed-in user's fsc_* bearer token: Authorization: Bearer ${AUTH_TOKEN}."}],"topics":[{"slug":"data","title":"Managed Data","url":"https://docs.fullstacked.cloud/data","markdownUrl":"https://docs.fullstacked.cloud/data?format=md","jsonUrl":"https://docs.fullstacked.cloud/data?format=json","markdown":"# Managed Data\n\nFullStacked Cloud runs PostgreSQL, Redis, MongoDB and S3-compatible object storage (RustFS) instances, each in its own container. A project reaches an instance linked to it through `fullstacked/tunnel`: register the tunnels hub with the user's `AUTH_TOKEN` and the instance's data token, then point the standard npm driver at the returned name. The runtime keeps a WebSocket session to the hub for that tunnel and carries every driver connection as a stream on it. Credentials come from `process.env` and match the current user's access (read-write or read-only), so the same code serves every user.\n\n## Setup\n\n1. Provision an instance and link it to the project: Console (https://console.fullstacked.cloud, Data, then Project, Linked data) or MCP (`data_provision`, then `data_link_project`).\n2. The project environment then carries the data token and credentials (see [Injected process.env](https://docs.fullstacked.cloud/project-runtime)):\n\n| Engine | Variables |\n| :--- | :--- |\n| PostgreSQL | `PG_DATA_TOKEN`, `DATA_USER`, `DATA_PASSWORD`, `DATA_DB` |\n| Redis | `REDIS_DATA_TOKEN`, `DATA_USER`, `DATA_PASSWORD` |\n| MongoDB | `MONGO_DATA_TOKEN`, `DATA_USER`, `DATA_PASSWORD`, `DATA_DB` |\n| S3 | `S3_DATA_TOKEN`, `DATA_KEY_ID`, `DATA_KEY_SECRET`, `DATA_BUCKET` |\n\nThe oldest linked instance uses these names; other instances add a prefix from their name (`ANALYTICS_DB_DATA_USER`, `ANALYTICS_DB_PG_DATA_TOKEN`, ...).\n\n3. Connect with the code below. Always `ssl: false` (the tunnel is already `wss://`); the port given to the driver is ignored (the token selects the instance), keep the engine's default for readability.\n\n## PostgreSQL (`pg`)\n\n```ts\n// db.ts\nimport tunnel from \"fullstacked/tunnel\";\nimport pg from \"pg\";\n\nlet pool: pg.Pool | null = null;\n\nexport async function getPool(): Promise<pg.Pool> {\n    if (pool) return pool;\n    const host = await tunnel.register({\n        host: process.env.TUNNELS_HOST!,\n        authorization: `${process.env.AUTH_TOKEN}:${process.env.PG_DATA_TOKEN}`,\n    });\n    pool = new pg.Pool({\n        host,\n        port: 5432,\n        user: process.env.DATA_USER,\n        password: process.env.DATA_PASSWORD,\n        database: process.env.DATA_DB,\n        ssl: false,\n        max: 10,\n    });\n    pool.on(\"error\", (err) => console.error(\"pg pool error\", err));\n    return pool;\n}\n\nconst { rows } = await (await getPool()).query(\"SELECT now()\");\n```\n\n## Redis (`ioredis`)\n\n```ts\nimport tunnel from \"fullstacked/tunnel\";\nimport Redis from \"ioredis\";\n\nconst host = await tunnel.register({\n    host: process.env.TUNNELS_HOST!,\n    authorization: `${process.env.AUTH_TOKEN}:${process.env.REDIS_DATA_TOKEN}`,\n});\nconst redis = new Redis({\n    host,\n    port: 6379,\n    username: process.env.DATA_USER,\n    password: process.env.DATA_PASSWORD,\n});\nredis.on(\"error\", (err) => console.error(\"redis error\", err));\nawait redis.set(\"greeting\", \"hello\");\n```\n\n## MongoDB (`mongodb`)\n\n```ts\nimport tunnel from \"fullstacked/tunnel\";\nimport { MongoClient } from \"mongodb\";\n\nconst host = await tunnel.register({\n    host: process.env.TUNNELS_HOST!,\n    authorization: `${process.env.AUTH_TOKEN}:${process.env.MONGO_DATA_TOKEN}`,\n});\nconst { DATA_USER, DATA_PASSWORD, DATA_DB } = process.env;\nconst client = new MongoClient(\n    `mongodb://${DATA_USER}:${DATA_PASSWORD}@${host}:27017/${DATA_DB}?authSource=${DATA_DB}&directConnection=true`\n);\nawait client.connect();\nconst db = client.db(DATA_DB);\n```\n\nKeep `directConnection=true` (a single server reached through the tunnel name) and `authSource` set to the database: the app users live there.\n\n## S3 (`@aws-sdk/client-s3`)\n\n```ts\nimport tunnel from \"fullstacked/tunnel\";\nimport { S3Client, PutObjectCommand, ListObjectsV2Command } from \"@aws-sdk/client-s3\";\n\nconst host = await tunnel.register({\n    host: process.env.TUNNELS_HOST!,\n    authorization: `${process.env.AUTH_TOKEN}:${process.env.S3_DATA_TOKEN}`,\n});\nconst s3 = new S3Client({\n    endpoint: `http://${host}`,\n    region: \"us-east-1\",\n    forcePathStyle: true,\n    credentials: {\n        accessKeyId: process.env.DATA_KEY_ID!,\n        secretAccessKey: process.env.DATA_KEY_SECRET!,\n    },\n});\nconst Bucket = process.env.DATA_BUCKET!;\nawait s3.send(new PutObjectCommand({ Bucket, Key: \"hello.txt\", Body: \"hi\", ContentType: \"text/plain\" }));\nconst list = await s3.send(new ListObjectsV2Command({ Bucket }));\n```\n\nUse `http://` (not `https://`) and `forcePathStyle: true`. Each instance has one bucket, `DATA_BUCKET`.\n\n## Read-only users\n\nA user with read access only receives the `app_ro` credentials and the read-only token: PostgreSQL rejects writes (`SQLSTATE 25006` read-only transaction, or `42501` insufficient privilege), Redis allows only read and subscribe commands, MongoDB and S3 refuse writes. A frozen instance (over quota) also resolves to read-only credentials for everyone. Design the UI to handle a failed write, and gate write controls with `GET /api/v1/user/access`: `resources.data[<instanceId>].write && !resources.data[<instanceId>].frozen` (`write` stays true on a frozen instance).\n\n## Container limits\n\n| Engine | Image | Port | Memory | CPUs | PIDs |\n| :--- | :--- | :--- | :--- | :--- | :--- |\n| PostgreSQL | `postgres:18.6-alpine` | `5432` | 256 MB | 2 | 256 |\n| Redis | `redis:8.10.2-alpine` | `6379` | 256 MB | 1 | 128 |\n| MongoDB | `mongo:8.3.11-noble` | `27017` | 512 MB | 2 | 512 |\n| S3 (RustFS) | `rustfs/rustfs:1.0.0` | `9000` | 768 MB | 2 | 1024 |\n\nEngine settings:\n\n- **PostgreSQL**: `shared_buffers=32MB`, `maintenance_work_mem=16MB`, `max_connections=110`, and each backend's private memory capped at 64 MB. A query that needs more fails alone with `out of memory` (SQLSTATE `53200`); if backends together exceed the container memory, one is killed and PostgreSQL resets every connection of the instance. A query's temporary files stop at 1 GB (`temp_file_limit`, SQLSTATE `53400` for that query alone).\n- **Redis**: AOF persistence (`appendonly yes`) and `maxmemory 192mb` with `noeviction`: past 192 MB of data, writes fail with an `OOM` error while reads keep working; nothing is evicted. Keep keys bounded (TTLs).\n- **MongoDB**: default settings inside the container memory limit (diagnostic data capped at 20 MB).\n- **S3 (RustFS)**: at most 8 concurrent disk reads and 4 concurrent uploads per instance; further uploads queue for up to 60 s.\n\nContainers have no swap and restart automatically after being killed.\n\n## Fair use and driver practices\n\n1. **Page large results**: `LIMIT` with keyset pagination or cursors in PostgreSQL so no single query needs more than 64 MB of backend memory; `SCAN` instead of `KEYS *` in Redis; batch MongoDB reads with `limit` and cursors.\n2. **Bound concurrency**: keep `pg.Pool` small (`max` 10 or less per app) and avoid many heavy queries at once (`Promise.all` over dozens of large result sets) that together exceed the instance memory.\n3. **Bound uploads**: run at most 4 S3 uploads at a time per instance and keep the AWS SDK's default retries.\n4. **Handle errors**: attach `error` listeners to pools and clients so a dropped connection (revoked access, instance restart) does not surface as an unhandled error; reconnect on demand.\n5. **Register once**: call `tunnel.register` once per instance and reuse the name and the client.\n6. **Plan quotas**: on the Base plan an owner keeps 5 data instances, 1 GiB of storage across their projects and data instances together, and 5 GiB per UTC calendar month of data tunnel transfer (both directions). Instances beyond the 5 oldest are frozen (read-only) until others are deleted; over 1 GiB of storage every non-sponsored instance is frozen until storage falls below 95% of it; once the monthly transfer is spent, new connections are refused (`402 quota_exceeded`) until the next month. A Pro owner, or a Pro member accepted on a project the instance is linked to, lifts the limits.\n\n## Troubleshooting\n\nA refused tunnel session fails every connection with an error naming the tunnel and the WebSocket handshake status (for example `expected handshake response status code 101 but got 401`); the reason word below is in the hub's response body, not in the error.\n\n| Symptom | Cause |\n| :--- | :--- |\n| Variable undefined | Instance not linked, or the user has no access to it (omitted from the environment). |\n| Connection fails, status `401` | `AUTH_TOKEN` missing, expired or revoked, or the data token was rolled (re-read the environment). |\n| Status `403` (`read_access_denied` / `write_access_denied`) | The user lost access to the instance. |\n| Status `402` (`quota_exceeded`) | Frozen instance (read-write token) or monthly data transfer spent. |\n| Status `503` (`auth_unavailable`) | The hub could not check the token: retry. |\n| SSL negotiation error | Driver TLS enabled: set `ssl: false` / plain `http://`. |\n\n## Endpoints\n\n### GET /api/v1/data\n\nManaged data instances visible to the caller (?engine=).\n\n- **Service**: `data` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\ncurl:\n\n```bash\ncurl -sS -H \"Authorization: Bearer $AUTH_TOKEN\" \"https://tunnels.fullstacked.cloud/api/v1/data\"\n```\n\n### POST /api/v1/data/provision\n\nProvisions an isolated instance { engine: postgres | redis | mongodb | s3, label? } (201).\n\n- **Service**: `data` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\nRequest:\n\n```json\n{\n  \"engine\": \"postgres\"\n}\n```\n\n### GET /api/v1/data/:id\n\nOne instance.\n\n- **Service**: `data` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### DELETE /api/v1/data/:id\n\nDeprovisions an instance (owner only, 204).\n\n- **Service**: `data` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /api/v1/data/:id/connection\n\nConnection parameters, DATA_* variables and fullstacked/tunnel snippets for ?access_level=rw|ro within the caller's permission.\n\n- **Service**: `data` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /api/v1/data/:id/schema\n\nIntrospects tables, keys, collections or buckets (?schema=).\n\n- **Service**: `data` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### POST /api/v1/data/:id/query\n\nRuns one operation: { sql, params? } (postgres), { command } (redis), { operation, collection, ... } (mongodb) or { operation, key?, prefix?, ... } (s3).\n\n- **Service**: `data` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\nRequest:\n\n```json\n{\n  \"sql\": \"select now()\"\n}\n```\n"}],"endpoints":[{"service":"data","topic":"data","auth":"Bearer fsc_*","id":"data.list","method":"GET","path":"/api/v1/data","summary":"Managed data instances visible to the caller (?engine=).","codeExamples":{"curl":"curl -sS -H \"Authorization: Bearer $AUTH_TOKEN\" \"https://tunnels.fullstacked.cloud/api/v1/data\""},"fullUrl":"https://tunnels.fullstacked.cloud/api/v1/data"},{"service":"data","topic":"data","auth":"Bearer fsc_*","id":"data.provision","method":"POST","path":"/api/v1/data/provision","summary":"Provisions an isolated instance { engine: postgres | redis | mongodb | s3, label? } (201).","requestExample":{"engine":"postgres"},"fullUrl":"https://tunnels.fullstacked.cloud/api/v1/data/provision"},{"service":"data","topic":"data","auth":"Bearer fsc_*","id":"data.get","method":"GET","path":"/api/v1/data/:id","summary":"One instance.","fullUrl":"https://tunnels.fullstacked.cloud/api/v1/data/:id"},{"service":"data","topic":"data","auth":"Bearer fsc_*","id":"data.deprovision","method":"DELETE","path":"/api/v1/data/:id","summary":"Deprovisions an instance (owner only, 204).","fullUrl":"https://tunnels.fullstacked.cloud/api/v1/data/:id"},{"service":"data","topic":"data","auth":"Bearer fsc_*","id":"data.connection","method":"GET","path":"/api/v1/data/:id/connection","summary":"Connection parameters, DATA_* variables and fullstacked/tunnel snippets for ?access_level=rw|ro within the caller's permission.","fullUrl":"https://tunnels.fullstacked.cloud/api/v1/data/:id/connection"},{"service":"data","topic":"data","auth":"Bearer fsc_*","id":"data.schema","method":"GET","path":"/api/v1/data/:id/schema","summary":"Introspects tables, keys, collections or buckets (?schema=).","fullUrl":"https://tunnels.fullstacked.cloud/api/v1/data/:id/schema"},{"service":"data","topic":"data","auth":"Bearer fsc_*","id":"data.query","method":"POST","path":"/api/v1/data/:id/query","summary":"Runs one operation: { sql, params? } (postgres), { command } (redis), { operation, collection, ... } (mongodb) or { operation, key?, prefix?, ... } (s3).","requestExample":{"sql":"select now()"},"fullUrl":"https://tunnels.fullstacked.cloud/api/v1/data/:id/query"}]}