{
  "topic": "quickstart",
  "path": [
    "quickstart"
  ],
  "title": "cyoda quickstart — minimum invocations",
  "synopsis": "cyoda-go is a single-process, multi-tenant REST and gRPC API server backed by a pluggable embedded database management system. Storage backends are `memory`, `sqlite`, and `postgres`; authentication modes are `mock` and `jwt`. All configuration is via environment variables with a `CYODA_` prefix.",
  "body": "# quickstart\n\n## NAME\n\nquickstart — minimum commands to run a cyoda-go server.\n\n## SYNOPSIS\n\n```\ncyoda init && cyoda\n```\n\n## DESCRIPTION\n\ncyoda-go is a single-process, multi-tenant REST and gRPC API server backed by a pluggable embedded database management system. Storage backends are `memory`, `sqlite`, and `postgres`; authentication modes are `mock` and `jwt`. All configuration is via environment variables with a `CYODA_` prefix.\n\nThe binary starts with no required configuration. Default mode: sqlite storage (enabled by `cyoda init`), mock IAM, REST on port 8080, gRPC on port 9090, admin on port 9091.\n\n## DEFAULTS\n\nWithout any environment variables, after `cyoda init`:\n\n- `CYODA_STORAGE_BACKEND` = `memory` (before `cyoda init`; `sqlite` after init writes the user config)\n- `CYODA_SQLITE_PATH` = `~/.local/share/cyoda/cyoda.db` (Linux/macOS XDG; `%LocalAppData%\\cyoda\\cyoda.db` on Windows)\n- `CYODA_SQLITE_AUTO_MIGRATE` = `true`\n- `CYODA_SQLITE_BUSY_TIMEOUT` = `5s`\n- `CYODA_SQLITE_CACHE_SIZE` = `64000` (KiB)\n- `CYODA_SQLITE_SEARCH_SCAN_LIMIT` = `100000`\n- `CYODA_IAM_MODE` = `mock` (all requests accepted without authentication)\n- `CYODA_IAM_MOCK_ROLES` = `ROLE_ADMIN,ROLE_M2M`\n- `CYODA_HTTP_PORT` = `8080`\n- `CYODA_GRPC_PORT` = `9090`\n- `CYODA_ADMIN_PORT` = `9091`\n- `CYODA_ADMIN_BIND_ADDRESS` = `127.0.0.1`\n- `CYODA_CONTEXT_PATH` = `/api`\n- `CYODA_LOG_LEVEL` = `info`\n- `CYODA_REQUIRE_JWT` = `false`\n- `CYODA_OTEL_ENABLED` = `false`\n- `CYODA_CLUSTER_ENABLED` = `false`\n- `CYODA_SUPPRESS_BANNER` = `false`\n\nThe admin listener binds to `127.0.0.1` by default. `/livez` and `/readyz` are unauthenticated.\n\n**Warning:** mock auth accepts all requests without a token. An instance in mock mode must not be exposed to untrusted networks.\n\n## PRODUCTION CHECKLIST\n\nEnv vars required to move from defaults to a production-shaped deployment.\n\n**Storage — switch from sqlite to postgres:**\n\n- `CYODA_STORAGE_BACKEND` = `postgres` (required)\n- `CYODA_POSTGRES_URL` = `postgres://user:pass@host:5432/dbname` (required when backend is postgres)\n- `CYODA_POSTGRES_URL_FILE` — file path for `CYODA_POSTGRES_URL`; takes precedence over the plain var\n- `CYODA_POSTGRES_MAX_CONNS` = `25` (default)\n- `CYODA_POSTGRES_MIN_CONNS` = `5` (default)\n- `CYODA_POSTGRES_MAX_CONN_IDLE_TIME` = `5m` (default)\n- `CYODA_POSTGRES_AUTO_MIGRATE` = `true` (default)\n\n**Auth — switch from mock to JWT:**\n\n- `CYODA_IAM_MODE` = `jwt` (required)\n- `CYODA_REQUIRE_JWT` = `true` — refuse startup unless jwt mode and signing key are set\n- `CYODA_JWT_SIGNING_KEY` — RSA private key, PEM-encoded (required in jwt mode)\n- `CYODA_JWT_SIGNING_KEY_FILE` — file path for `CYODA_JWT_SIGNING_KEY`; takes precedence\n- `CYODA_JWT_ISSUER` = `cyoda` (default; set to your issuer URI)\n- `CYODA_JWT_AUDIENCE` = `` (default empty; set to require audience claim validation)\n- `CYODA_JWT_EXPIRY_SECONDS` = `3600` (default)\n\n**Inter-node dispatch auth (cluster mode):**\n\n- `CYODA_HMAC_SECRET` — hex-encoded HMAC secret for inter-node dispatch authentication\n- `CYODA_HMAC_SECRET_FILE` — file path for `CYODA_HMAC_SECRET`; takes precedence\n\n**Bootstrap M2M client (optional):**\n\n- `CYODA_BOOTSTRAP_CLIENT_ID` — M2M client ID to provision at startup\n- `CYODA_BOOTSTRAP_CLIENT_SECRET` — M2M client secret (required when client ID is set)\n- `CYODA_BOOTSTRAP_CLIENT_SECRET_FILE` — file path for `CYODA_BOOTSTRAP_CLIENT_SECRET`; takes precedence\n- `CYODA_BOOTSTRAP_TENANT_ID` = `default-tenant` (default)\n- `CYODA_BOOTSTRAP_USER_ID` = `admin` (default)\n- `CYODA_BOOTSTRAP_ROLES` = `ROLE_ADMIN,ROLE_M2M` (default)\n\n**Admin metrics auth (optional):**\n\n- `CYODA_METRICS_BEARER` — static bearer token required on `GET /metrics`\n- `CYODA_METRICS_REQUIRE_AUTH` = `false` (default; set `true` to enforce metrics auth at startup)\n\n### Generating secrets\n\n**JWT signing key** — RSA private key, PEM-encoded. The binary accepts PKCS#8 (`BEGIN PRIVATE KEY`) and PKCS#1 (`BEGIN RSA PRIVATE KEY`) formats. Only RSA keys are accepted; the signature algorithm is always RS256. Minimum recommended size: 2048 bits.\n\nGenerate a PKCS#8 RSA-2048 key (preferred):\n\n```\nopenssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out signing.pem\n```\n\nGenerate a PKCS#1 RSA-2048 key (also accepted):\n\n```\nopenssl genrsa -out signing.pem 2048\n```\n\nPass to the binary via file reference (recommended):\n\n```\nexport CYODA_JWT_SIGNING_KEY_FILE=/run/secrets/signing.pem\n```\n\n**HMAC secret** — hex-encoded shared secret for inter-node dispatch authentication. The binary decodes the value from hex to raw bytes. Minimum: 32 bytes of entropy (64 hex characters).\n\nGenerate a 32-byte secret (64 hex chars):\n\n```\nopenssl rand -hex 32\n```\n\nPass to the binary via file reference (recommended):\n\n```\n# Write the hex string to a file, then:\nexport CYODA_HMAC_SECRET_FILE=/run/secrets/hmac-secret\n```\n\nOr pass inline (dev only — not suitable for production):\n\n```\nexport CYODA_HMAC_SECRET=\"$(openssl rand -hex 32)\"\n```\n\n## EXAMPLES\n\n**First-run default (sqlite + mock auth):**\n\n```\ncyoda init\ncyoda\n```\n\n`cyoda init` writes `~/.config/cyoda/cyoda.env` with `CYODA_STORAGE_BACKEND=sqlite`. Running `cyoda` then loads that file and starts the server on port 8080. No other configuration required.\n\n**Postgres + mock auth:**\n\n```\nexport CYODA_STORAGE_BACKEND=postgres\nexport CYODA_POSTGRES_URL=postgres://cyoda:secret@localhost:5432/cyoda\ncyoda\n```\n\n**Postgres + JWT required (production-shaped):**\n\n```\nexport CYODA_STORAGE_BACKEND=postgres\nexport CYODA_POSTGRES_URL_FILE=/run/secrets/postgres-url\nexport CYODA_IAM_MODE=jwt\nexport CYODA_REQUIRE_JWT=true\nexport CYODA_JWT_SIGNING_KEY_FILE=/run/secrets/signing.pem\nexport CYODA_JWT_ISSUER=https://auth.example.com\nexport CYODA_JWT_AUDIENCE=cyoda-api\ncyoda\n```\n\n**Docker (sqlite + mock auth):**\n\n```\ndocker run --rm \\\n  -p 127.0.0.1:8080:8080 \\\n  -p 127.0.0.1:9090:9090 \\\n  -p 127.0.0.1:9091:9091 \\\n  -e CYODA_STORAGE_BACKEND=sqlite \\\n  -e CYODA_SQLITE_PATH=/var/lib/cyoda/cyoda.db \\\n  -e CYODA_ADMIN_BIND_ADDRESS=0.0.0.0 \\\n  -v cyoda-data:/var/lib/cyoda \\\n  ghcr.io/cyoda-platform/cyoda:latest\n```\n\nThe image pre-stages `/var/lib/cyoda` owned by UID 65532. The container runs as non-root (65532:65532). `/livez` and `/readyz` are served on port 9091. Set `CYODA_ADMIN_BIND_ADDRESS=0.0.0.0` so the health endpoint is reachable from outside the container.\n\n**Docker Compose (sqlite + mock auth):**\n\n```\ncurl -O https://raw.githubusercontent.com/cyoda-platform/cyoda-go/main/deploy/docker/compose.yaml\ndocker compose up\n```\n\nThe bundled `compose.yaml` at `deploy/docker/compose.yaml` uses `CYODA_STORAGE_BACKEND=sqlite` and a named volume `cyoda-data` for persistence. For production JWT auth, mount the signing key file and set `CYODA_JWT_SIGNING_KEY_FILE` — do not pass `CYODA_JWT_SIGNING_KEY` inline; multi-line PEM does not survive shell or YAML env-var interpolation. See `run` for the full docker compose JWT configuration.\n\n## SEE ALSO\n\n- cli\n- cli.init\n- config\n- config.database\n- config.auth\n- run\n",
  "sections": [
    {
      "name": "NAME",
      "body": "quickstart — minimum commands to run a cyoda-go server."
    },
    {
      "name": "SYNOPSIS",
      "body": "```\ncyoda init && cyoda\n```"
    },
    {
      "name": "DESCRIPTION",
      "body": "cyoda-go is a single-process, multi-tenant REST and gRPC API server backed by a pluggable embedded database management system. Storage backends are `memory`, `sqlite`, and `postgres`; authentication modes are `mock` and `jwt`. All configuration is via environment variables with a `CYODA_` prefix.\n\nThe binary starts with no required configuration. Default mode: sqlite storage (enabled by `cyoda init`), mock IAM, REST on port 8080, gRPC on port 9090, admin on port 9091."
    },
    {
      "name": "DEFAULTS",
      "body": "Without any environment variables, after `cyoda init`:\n\n- `CYODA_STORAGE_BACKEND` = `memory` (before `cyoda init`; `sqlite` after init writes the user config)\n- `CYODA_SQLITE_PATH` = `~/.local/share/cyoda/cyoda.db` (Linux/macOS XDG; `%LocalAppData%\\cyoda\\cyoda.db` on Windows)\n- `CYODA_SQLITE_AUTO_MIGRATE` = `true`\n- `CYODA_SQLITE_BUSY_TIMEOUT` = `5s`\n- `CYODA_SQLITE_CACHE_SIZE` = `64000` (KiB)\n- `CYODA_SQLITE_SEARCH_SCAN_LIMIT` = `100000`\n- `CYODA_IAM_MODE` = `mock` (all requests accepted without authentication)\n- `CYODA_IAM_MOCK_ROLES` = `ROLE_ADMIN,ROLE_M2M`\n- `CYODA_HTTP_PORT` = `8080`\n- `CYODA_GRPC_PORT` = `9090`\n- `CYODA_ADMIN_PORT` = `9091`\n- `CYODA_ADMIN_BIND_ADDRESS` = `127.0.0.1`\n- `CYODA_CONTEXT_PATH` = `/api`\n- `CYODA_LOG_LEVEL` = `info`\n- `CYODA_REQUIRE_JWT` = `false`\n- `CYODA_OTEL_ENABLED` = `false`\n- `CYODA_CLUSTER_ENABLED` = `false`\n- `CYODA_SUPPRESS_BANNER` = `false`\n\nThe admin listener binds to `127.0.0.1` by default. `/livez` and `/readyz` are unauthenticated.\n\n**Warning:** mock auth accepts all requests without a token. An instance in mock mode must not be exposed to untrusted networks."
    },
    {
      "name": "PRODUCTION CHECKLIST",
      "body": "Env vars required to move from defaults to a production-shaped deployment.\n\n**Storage — switch from sqlite to postgres:**\n\n- `CYODA_STORAGE_BACKEND` = `postgres` (required)\n- `CYODA_POSTGRES_URL` = `postgres://user:pass@host:5432/dbname` (required when backend is postgres)\n- `CYODA_POSTGRES_URL_FILE` — file path for `CYODA_POSTGRES_URL`; takes precedence over the plain var\n- `CYODA_POSTGRES_MAX_CONNS` = `25` (default)\n- `CYODA_POSTGRES_MIN_CONNS` = `5` (default)\n- `CYODA_POSTGRES_MAX_CONN_IDLE_TIME` = `5m` (default)\n- `CYODA_POSTGRES_AUTO_MIGRATE` = `true` (default)\n\n**Auth — switch from mock to JWT:**\n\n- `CYODA_IAM_MODE` = `jwt` (required)\n- `CYODA_REQUIRE_JWT` = `true` — refuse startup unless jwt mode and signing key are set\n- `CYODA_JWT_SIGNING_KEY` — RSA private key, PEM-encoded (required in jwt mode)\n- `CYODA_JWT_SIGNING_KEY_FILE` — file path for `CYODA_JWT_SIGNING_KEY`; takes precedence\n- `CYODA_JWT_ISSUER` = `cyoda` (default; set to your issuer URI)\n- `CYODA_JWT_AUDIENCE` = `` (default empty; set to require audience claim validation)\n- `CYODA_JWT_EXPIRY_SECONDS` = `3600` (default)\n\n**Inter-node dispatch auth (cluster mode):**\n\n- `CYODA_HMAC_SECRET` — hex-encoded HMAC secret for inter-node dispatch authentication\n- `CYODA_HMAC_SECRET_FILE` — file path for `CYODA_HMAC_SECRET`; takes precedence\n\n**Bootstrap M2M client (optional):**\n\n- `CYODA_BOOTSTRAP_CLIENT_ID` — M2M client ID to provision at startup\n- `CYODA_BOOTSTRAP_CLIENT_SECRET` — M2M client secret (required when client ID is set)\n- `CYODA_BOOTSTRAP_CLIENT_SECRET_FILE` — file path for `CYODA_BOOTSTRAP_CLIENT_SECRET`; takes precedence\n- `CYODA_BOOTSTRAP_TENANT_ID` = `default-tenant` (default)\n- `CYODA_BOOTSTRAP_USER_ID` = `admin` (default)\n- `CYODA_BOOTSTRAP_ROLES` = `ROLE_ADMIN,ROLE_M2M` (default)\n\n**Admin metrics auth (optional):**\n\n- `CYODA_METRICS_BEARER` — static bearer token required on `GET /metrics`\n- `CYODA_METRICS_REQUIRE_AUTH` = `false` (default; set `true` to enforce metrics auth at startup)\n\n### Generating secrets\n\n**JWT signing key** — RSA private key, PEM-encoded. The binary accepts PKCS#8 (`BEGIN PRIVATE KEY`) and PKCS#1 (`BEGIN RSA PRIVATE KEY`) formats. Only RSA keys are accepted; the signature algorithm is always RS256. Minimum recommended size: 2048 bits.\n\nGenerate a PKCS#8 RSA-2048 key (preferred):\n\n```\nopenssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out signing.pem\n```\n\nGenerate a PKCS#1 RSA-2048 key (also accepted):\n\n```\nopenssl genrsa -out signing.pem 2048\n```\n\nPass to the binary via file reference (recommended):\n\n```\nexport CYODA_JWT_SIGNING_KEY_FILE=/run/secrets/signing.pem\n```\n\n**HMAC secret** — hex-encoded shared secret for inter-node dispatch authentication. The binary decodes the value from hex to raw bytes. Minimum: 32 bytes of entropy (64 hex characters).\n\nGenerate a 32-byte secret (64 hex chars):\n\n```\nopenssl rand -hex 32\n```\n\nPass to the binary via file reference (recommended):\n\n```\n# Write the hex string to a file, then:\nexport CYODA_HMAC_SECRET_FILE=/run/secrets/hmac-secret\n```\n\nOr pass inline (dev only — not suitable for production):\n\n```\nexport CYODA_HMAC_SECRET=\"$(openssl rand -hex 32)\"\n```"
    },
    {
      "name": "EXAMPLES",
      "body": "**First-run default (sqlite + mock auth):**\n\n```\ncyoda init\ncyoda\n```\n\n`cyoda init` writes `~/.config/cyoda/cyoda.env` with `CYODA_STORAGE_BACKEND=sqlite`. Running `cyoda` then loads that file and starts the server on port 8080. No other configuration required.\n\n**Postgres + mock auth:**\n\n```\nexport CYODA_STORAGE_BACKEND=postgres\nexport CYODA_POSTGRES_URL=postgres://cyoda:secret@localhost:5432/cyoda\ncyoda\n```\n\n**Postgres + JWT required (production-shaped):**\n\n```\nexport CYODA_STORAGE_BACKEND=postgres\nexport CYODA_POSTGRES_URL_FILE=/run/secrets/postgres-url\nexport CYODA_IAM_MODE=jwt\nexport CYODA_REQUIRE_JWT=true\nexport CYODA_JWT_SIGNING_KEY_FILE=/run/secrets/signing.pem\nexport CYODA_JWT_ISSUER=https://auth.example.com\nexport CYODA_JWT_AUDIENCE=cyoda-api\ncyoda\n```\n\n**Docker (sqlite + mock auth):**\n\n```\ndocker run --rm \\\n  -p 127.0.0.1:8080:8080 \\\n  -p 127.0.0.1:9090:9090 \\\n  -p 127.0.0.1:9091:9091 \\\n  -e CYODA_STORAGE_BACKEND=sqlite \\\n  -e CYODA_SQLITE_PATH=/var/lib/cyoda/cyoda.db \\\n  -e CYODA_ADMIN_BIND_ADDRESS=0.0.0.0 \\\n  -v cyoda-data:/var/lib/cyoda \\\n  ghcr.io/cyoda-platform/cyoda:latest\n```\n\nThe image pre-stages `/var/lib/cyoda` owned by UID 65532. The container runs as non-root (65532:65532). `/livez` and `/readyz` are served on port 9091. Set `CYODA_ADMIN_BIND_ADDRESS=0.0.0.0` so the health endpoint is reachable from outside the container.\n\n**Docker Compose (sqlite + mock auth):**\n\n```\ncurl -O https://raw.githubusercontent.com/cyoda-platform/cyoda-go/main/deploy/docker/compose.yaml\ndocker compose up\n```\n\nThe bundled `compose.yaml` at `deploy/docker/compose.yaml` uses `CYODA_STORAGE_BACKEND=sqlite` and a named volume `cyoda-data` for persistence. For production JWT auth, mount the signing key file and set `CYODA_JWT_SIGNING_KEY_FILE` — do not pass `CYODA_JWT_SIGNING_KEY` inline; multi-line PEM does not survive shell or YAML env-var interpolation. See `run` for the full docker compose JWT configuration."
    },
    {
      "name": "SEE ALSO",
      "body": "- cli\n- cli.init\n- config\n- config.database\n- config.auth\n- run"
    }
  ],
  "see_also": [
    "cli",
    "cli.init",
    "config",
    "config.database",
    "config.auth",
    "run"
  ],
  "stability": "stable",
  "actions": []
}
