{
  "manifest_version": "1",
  "slug": "docker-compose",
  "title": "Docker Compose",
  "headline": "Deploy a Docker Compose app from GitHub",
  "description": "Deploy any Docker Compose app from GitHub to an exe.dev VM: git push to build and deploy, HTTPS, volumes on a persistent disk, nightly database dumps.",
  "category": "frameworks",
  "tags": [
    "docker compose",
    "deploy docker compose",
    "compose.yaml",
    "git push deploy",
    "docker",
    "self-hosting",
    "postgresql"
  ],
  "url": "https://exe.dev/deploy/docker-compose",
  "markdown_url": "https://exe-staging.dev/deploy/docker-compose.md",
  "upstream": {
    "website": "https://docs.docker.com/compose/",
    "source": "https://github.com/docker/compose",
    "docs": "https://docs.docker.com/reference/compose-file/",
    "license": "Apache-2.0"
  },
  "example_vm_name": "my-docker-compose",
  "time": "10 minutes",
  "stack": [
    {
      "name": "Your Compose app",
      "detail": "Built from your repo on every git push; any language",
      "port": 8000,
      "data": "~/data"
    },
    {
      "name": "Its other services",
      "detail": "Databases, caches and workers from compose.yaml, on private ports"
    },
    {
      "name": "Deploy hook",
      "detail": "Builds, starts, and prunes old images on git push",
      "data": "~/app.git"
    },
    {
      "name": "Nightly backup",
      "detail": "Database dumps, ~/data and settings; keeps 14",
      "data": "~/backups"
    }
  ],
  "access": [
    {
      "url": "https://my-docker-compose.exe.xyz/",
      "who": "private",
      "what": "The service you publish on port 8000; public only if you choose"
    },
    {
      "url": "https://my-docker-compose.exe.xyz:8081/",
      "who": "private",
      "what": "Any other service you publish on a port from 3000 to 9999"
    },
    {
      "url": "ssh my-docker-compose.exe.xyz",
      "who": "private",
      "what": "git push to deploy; shell and logs"
    }
  ],
  "inputs": [
    {
      "name": "REPO",
      "description": "the GitHub repository to deploy, as owner/name or a URL, or `new` to start a fresh Compose app (a small web app and PostgreSQL)"
    }
  ],
  "delivers": [
    "Your Compose app, built from your repo (or a fresh web app with PostgreSQL), served on port 8000.",
    "git push to deploy; a push that fails to build or start is rejected, and the running version stays up.",
    "Docker enabled at boot and a restart policy on every long-running service, so the app survives a reboot.",
    "Nightly backups (database dumps, ~/data and settings), with a restore tested."
  ],
  "owner_runs_after": [
    "git clone -o exe my-docker-compose.exe.xyz:app.git my-app   # if you started fresh",
    "git remote add exe my-docker-compose.exe.xyz:app.git        # or, in your own repo's checkout"
  ],
  "deployable": true,
  "deploy": {
    "new_vm_url": "https://exe-staging.dev/new?prompt=REPO%3D%3Cfill-this-in%3E%0A%0ASet%20up%20Docker%20Compose%20on%20this%20VM%20by%20following%20the%20exe.dev%20recipe%20at%20https%3A%2F%2Fexe-staging.dev%2Fdeploy%2Fdocker-compose.md%20from%20top%20to%20bottom.%0A%0AThe%20values%20at%20the%20top%20of%20this%20message%20are%20mine%3A%0A-%20REPO%3A%20the%20GitHub%20repository%20to%20deploy%2C%20as%20owner%2Fname%20or%20a%20URL%2C%20or%20%60new%60%20to%20start%20a%20fresh%20Compose%20app%20%28a%20small%20web%20app%20and%20PostgreSQL%29%0AAsk%20me%20for%20any%20that%20still%20say%20%3Cfill-this-in%3E%20before%20you%20need%20them.%0A%0AThe%20recipe%27s%20example%20VM%20name%20is%20my-docker-compose.%20This%20VM%27s%20name%20is%20the%20output%20of%20%60hostname%60%3A%20use%20it%20wherever%20the%20recipe%20writes%20my-docker-compose.%20Skip%20the%20step%20that%20creates%20the%20VM%3B%20you%20are%20on%20it.%0A%0AYou%20are%20done%20when%20all%20of%20these%20are%20true.%20Check%20each%20one%20yourself%3A%0A-%20Your%20Compose%20app%2C%20built%20from%20your%20repo%20%28or%20a%20fresh%20web%20app%20with%20PostgreSQL%29%2C%20served%20on%20port%208000.%0A-%20git%20push%20to%20deploy%3B%20a%20push%20that%20fails%20to%20build%20or%20start%20is%20rejected%2C%20and%20the%20running%20version%20stays%20up.%0A-%20Docker%20enabled%20at%20boot%20and%20a%20restart%20policy%20on%20every%20long-running%20service%2C%20so%20the%20app%20survives%20a%20reboot.%0A-%20Nightly%20backups%20%28database%20dumps%2C%20~%2Fdata%20and%20settings%29%2C%20with%20a%20restore%20tested.%0A%0ARead%20the%20whole%20recipe%2C%20including%20the%20closed%20details%2C%20and%20follow%0A%22Install%20by%20hand%22%20in%20order%2C%20skipping%20VM%20creation%3A%20set%20up%20Docker%2C%20make%0Athe%20deploy%20repository%2C%20then%20%22Start%20fresh%22%20if%20REPO%20is%20new%20or%20%22Bring%20your%0Arepo%22%20otherwise%2C%20then%20backups.%20The%20walkthrough%20above%20it%20is%20for%20the%0Aowner%20afterwards.%20Don%27t%20run%20the%20later%20Restore%20or%20Update%20steps%2C%20don%27t%0Areboot%2C%20and%20leave%20sharing%20private.%0AWith%20a%20repo%2C%20never%20change%20it%20on%20GitHub%3A%20VM-only%20changes%20go%20in%0A~%2Fapp.override.yaml%20and%20settings%20in%20~%2Fapp.env.%20Generate%20passwords%20and%0Akeys%3B%20ask%20the%20owner%20for%20values%20only%20they%20have%2C%20like%20third-party%20API%0Akeys.%20If%20the%20clone%20fails%2C%20give%20the%20owner%20the%20GitHub%20integration%20command%0Afrom%20%22Bring%20your%20repo%22%20with%20this%20VM%27s%20name%20and%20the%20repo%2C%20and%20wait.%0AWrite%20app-backup.sh%20for%20the%20databases%20the%20stack%20really%20runs%2C%20and%20test%0Aa%20restore%20into%20a%20scratch%20database.%20To%20check%20a%20failing%20push%2C%20push%20a%0Acommit%20that%20breaks%20the%20build%20from%20a%20scratch%20clone%3A%20it%20must%20be%0Arejected.%20Remove%20your%20scratch%20clones%20and%20test%20scripts%20when%20done.%0AFinish%20with%20https%3A%2F%2F%3Cvm%3E.exe.xyz%2F%2C%20any%20other%20ports%20the%20app%20publishes%2C%0Aand%20only%20the%20one%20git%20command%20from%20%22Deploy%20with%20git%20push%22%20that%20fits%3A%0A%60git%20clone%20-o%20exe%60%20for%20REPO%3Dnew%2C%20%60git%20remote%20add%20exe%60%20for%20a%20repository.%0A%0AFinish%20with%20the%20URLs%20I%20should%20open%20and%20these%20commands%2C%20which%20only%20I%20can%20run%2C%20from%20my%20own%20computer%20%28with%20this%20VM%27s%20name%20in%20them%29%3A%0A%20%20git%20clone%20-o%20exe%20my-docker-compose.exe.xyz%3Aapp.git%20my-app%20%20%20%23%20if%20you%20started%20fresh%0A%20%20git%20remote%20add%20exe%20my-docker-compose.exe.xyz%3Aapp.git%20%20%20%20%20%20%20%20%23%20or%2C%20in%20your%20own%20repo%27s%20checkout%0A",
    "terminal": "curl -fsSL https://exe-staging.dev/deploy/docker-compose/prompt.txt | ssh exe.dev new --prompt=/dev/stdin",
    "prompt": "REPO=\u003cfill-this-in\u003e\n\nSet up Docker Compose on this VM by following the exe.dev recipe at https://exe-staging.dev/deploy/docker-compose.md from top to bottom.\n\nThe values at the top of this message are mine:\n- REPO: the GitHub repository to deploy, as owner/name or a URL, or `new` to start a fresh Compose app (a small web app and PostgreSQL)\nAsk me for any that still say \u003cfill-this-in\u003e before you need them.\n\nThe recipe's example VM name is my-docker-compose. This VM's name is the output of `hostname`: use it wherever the recipe writes my-docker-compose. Skip the step that creates the VM; you are on it.\n\nYou are done when all of these are true. Check each one yourself:\n- Your Compose app, built from your repo (or a fresh web app with PostgreSQL), served on port 8000.\n- git push to deploy; a push that fails to build or start is rejected, and the running version stays up.\n- Docker enabled at boot and a restart policy on every long-running service, so the app survives a reboot.\n- Nightly backups (database dumps, ~/data and settings), with a restore tested.\n\nRead the whole recipe, including the closed details, and follow\n\"Install by hand\" in order, skipping VM creation: set up Docker, make\nthe deploy repository, then \"Start fresh\" if REPO is new or \"Bring your\nrepo\" otherwise, then backups. The walkthrough above it is for the\nowner afterwards. Don't run the later Restore or Update steps, don't\nreboot, and leave sharing private.\nWith a repo, never change it on GitHub: VM-only changes go in\n~/app.override.yaml and settings in ~/app.env. Generate passwords and\nkeys; ask the owner for values only they have, like third-party API\nkeys. If the clone fails, give the owner the GitHub integration command\nfrom \"Bring your repo\" with this VM's name and the repo, and wait.\nWrite app-backup.sh for the databases the stack really runs, and test\na restore into a scratch database. To check a failing push, push a\ncommit that breaks the build from a scratch clone: it must be\nrejected. Remove your scratch clones and test scripts when done.\nFinish with https://\u003cvm\u003e.exe.xyz/, any other ports the app publishes,\nand only the one git command from \"Deploy with git push\" that fits:\n`git clone -o exe` for REPO=new, `git remote add exe` for a repository.\n\nFinish with the URLs I should open and these commands, which only I can run, from my own computer (with this VM's name in them):\n  git clone -o exe my-docker-compose.exe.xyz:app.git my-app   # if you started fresh\n  git remote add exe my-docker-compose.exe.xyz:app.git        # or, in your own repo's checkout\n",
    "prompt_url": "https://exe-staging.dev/deploy/docker-compose/prompt.txt"
  },
  "updated": "2026-10-17",
  "verified": "2026-10-17",
  "related": [
    "docker-host",
    "postgres",
    "n8n",
    "uptime-kuma",
    "django",
    "laravel"
  ],
  "alternatives": [
    "Railway",
    "Render",
    "Coolify"
  ]
}
