{
  "slug": "install/docker",
  "title": "Install with Docker",
  "description": "Deploy Cuitty on a single VM using Docker Compose. Recommended for solo developers and small teams.",
  "url": "https://cuitty.com/docs/install/docker",
  "markdown_url": "https://cuitty.com/docs/install/docker.md",
  "json_url": "https://cuitty.com/docs/install/docker.json",
  "frontmatter": {
    "title": "Install with Docker",
    "description": "Deploy Cuitty on a single VM using Docker Compose. Recommended for solo developers and small teams.",
    "order": 1,
    "section": "Install",
    "updatedAt": "2026-04-27"
  },
  "headings": [
    {
      "depth": 1,
      "slug": "install-with-docker-compose",
      "text": "Install with Docker Compose"
    },
    {
      "depth": 2,
      "slug": "reference-compose-file",
      "text": "Reference compose file"
    },
    {
      "depth": 2,
      "slug": "backups",
      "text": "Backups"
    },
    {
      "depth": 2,
      "slug": "upgrades",
      "text": "Upgrades"
    },
    {
      "depth": 2,
      "slug": "see-also",
      "text": "See also"
    }
  ],
  "body_markdown": "# Install with Docker Compose\n\nDocker Compose is the fastest way to run Cuitty in production-like conditions on a single VM. The reference compose file lives at the root of the `cuitty` repo.\n\n## Reference compose file\n\n```yaml\nversion: \"3.9\"\n\nservices:\n  postgres:\n    image: postgres:16\n    environment:\n      POSTGRES_USER: cuitty\n      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}\n      POSTGRES_DB: cuitty\n    volumes:\n      - pgdata:/var/lib/postgresql/data\n    healthcheck:\n      test: [\"CMD-SHELL\", \"pg_isready -U cuitty\"]\n      interval: 5s\n      timeout: 3s\n      retries: 12\n\n  spicedb:\n    image: authzed/spicedb:latest\n    command: serve --grpc-preshared-key ${SPICEDB_PRESHARED_KEY}\n    ports:\n      - \"50051:50051\"\n\n  portal:\n    image: ghcr.io/cuitty/portal:latest\n    depends_on:\n      postgres: { condition: service_healthy }\n      spicedb: { condition: service_started }\n    ports:\n      - \"7700:7700\"\n    environment:\n      DATABASE_URL: postgres://cuitty:${POSTGRES_PASSWORD}@postgres:5432/cuitty\n      SPICEDB_URL: spicedb:50051\n      SPICEDB_TOKEN: ${SPICEDB_PRESHARED_KEY}\n      BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}\n\nvolumes:\n  pgdata:\n```\n\nCreate a `.env` file with `POSTGRES_PASSWORD`, `SPICEDB_PRESHARED_KEY`, and a 32-byte random `BETTER_AUTH_SECRET`. Then:\n\n```bash\ndocker compose up -d\n```\n\n## Backups\n\nThe portal stores nothing important on its own filesystem — every byte that matters lives in the `postgres` and `libsql` volumes. Snapshot the named volumes nightly with `pg_dump` and a `tar` of the libSQL data directory.\n\n## Upgrades\n\n```bash\ndocker compose pull portal\ndocker compose up -d portal\n```\n\nThe portal runs migrations on startup. Always snapshot first.\n\n## See also\n\n- [Install on Kubernetes](/docs/install/kubernetes)\n- [Install on bare metal](/docs/install/bare-metal)\n- [Install on cloud providers](/docs/install/cloud)",
  "body_html": "<h1 id=\"install-with-docker-compose\">Install with Docker Compose</h1>\n<p>Docker Compose is the fastest way to run Cuitty in production-like conditions on a single VM. The reference compose file lives at the root of the <code>cuitty</code> repo.</p>\n<h2 id=\"reference-compose-file\">Reference compose file</h2>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8; overflow-x: auto;\" tabindex=\"0\" data-language=\"yaml\"><code><span class=\"line\"><span style=\"color:#85E89D\">version</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"3.9\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#85E89D\">services</span><span style=\"color:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">  postgres</span><span style=\"color:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">    image</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">postgres:16</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">    environment</span><span style=\"color:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">      POSTGRES_USER</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">cuitty</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">      POSTGRES_PASSWORD</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">${POSTGRES_PASSWORD}</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">      POSTGRES_DB</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">cuitty</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">    volumes</span><span style=\"color:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">      - </span><span style=\"color:#9ECBFF\">pgdata:/var/lib/postgresql/data</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">    healthcheck</span><span style=\"color:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">      test</span><span style=\"color:#E1E4E8\">: [</span><span style=\"color:#9ECBFF\">\"CMD-SHELL\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#9ECBFF\">\"pg_isready -U cuitty\"</span><span style=\"color:#E1E4E8\">]</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">      interval</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">5s</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">      timeout</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">3s</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">      retries</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#79B8FF\">12</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#85E89D\">  spicedb</span><span style=\"color:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">    image</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">authzed/spicedb:latest</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">    command</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">serve --grpc-preshared-key ${SPICEDB_PRESHARED_KEY}</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">    ports</span><span style=\"color:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">      - </span><span style=\"color:#9ECBFF\">\"50051:50051\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#85E89D\">  portal</span><span style=\"color:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">    image</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">ghcr.io/cuitty/portal:latest</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">    depends_on</span><span style=\"color:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">      postgres</span><span style=\"color:#E1E4E8\">: { </span><span style=\"color:#85E89D\">condition</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">service_healthy</span><span style=\"color:#E1E4E8\"> }</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">      spicedb</span><span style=\"color:#E1E4E8\">: { </span><span style=\"color:#85E89D\">condition</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">service_started</span><span style=\"color:#E1E4E8\"> }</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">    ports</span><span style=\"color:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">      - </span><span style=\"color:#9ECBFF\">\"7700:7700\"</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">    environment</span><span style=\"color:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">      DATABASE_URL</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">postgres://cuitty:${POSTGRES_PASSWORD}@postgres:5432/cuitty</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">      SPICEDB_URL</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">spicedb:50051</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">      SPICEDB_TOKEN</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">${SPICEDB_PRESHARED_KEY}</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">      BETTER_AUTH_SECRET</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">${BETTER_AUTH_SECRET}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#85E89D\">volumes</span><span style=\"color:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">  pgdata</span><span style=\"color:#E1E4E8\">:</span></span></code></pre>\n<p>Create a <code>.env</code> file with <code>POSTGRES_PASSWORD</code>, <code>SPICEDB_PRESHARED_KEY</code>, and a 32-byte random <code>BETTER_AUTH_SECRET</code>. Then:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8; overflow-x: auto;\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#B392F0\">docker</span><span style=\"color:#9ECBFF\"> compose</span><span style=\"color:#9ECBFF\"> up</span><span style=\"color:#79B8FF\"> -d</span></span></code></pre>\n<h2 id=\"backups\">Backups</h2>\n<p>The portal stores nothing important on its own filesystem — every byte that matters lives in the <code>postgres</code> and <code>libsql</code> volumes. Snapshot the named volumes nightly with <code>pg_dump</code> and a <code>tar</code> of the libSQL data directory.</p>\n<h2 id=\"upgrades\">Upgrades</h2>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8; overflow-x: auto;\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#B392F0\">docker</span><span style=\"color:#9ECBFF\"> compose</span><span style=\"color:#9ECBFF\"> pull</span><span style=\"color:#9ECBFF\"> portal</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">docker</span><span style=\"color:#9ECBFF\"> compose</span><span style=\"color:#9ECBFF\"> up</span><span style=\"color:#79B8FF\"> -d</span><span style=\"color:#9ECBFF\"> portal</span></span></code></pre>\n<p>The portal runs migrations on startup. Always snapshot first.</p>\n<h2 id=\"see-also\">See also</h2>\n<ul>\n<li><a href=\"/docs/install/kubernetes\">Install on Kubernetes</a></li>\n<li><a href=\"/docs/install/bare-metal\">Install on bare metal</a></li>\n<li><a href=\"/docs/install/cloud\">Install on cloud providers</a></li>\n</ul>",
  "links_out": [
    "/docs/install/kubernetes",
    "/docs/install/bare-metal",
    "/docs/install/cloud"
  ]
}