{
  "manifest_version": "1",
  "slug": "go",
  "title": "Go",
  "headline": "Deploy a Go web app",
  "description": "Deploy a Go web app to an exe.dev VM: git push builds it, systemd runs the binary, SQLite sits on a persistent disk, and exe.dev adds HTTPS.",
  "category": "frameworks",
  "tags": [
    "go",
    "golang",
    "net/http",
    "gin",
    "echo",
    "chi",
    "sqlite",
    "systemd",
    "git push deploy",
    "heroku alternative"
  ],
  "url": "https://exe.dev/deploy/go",
  "markdown_url": "https://exe.dev/deploy/go.md",
  "upstream": {
    "website": "https://go.dev",
    "source": "https://github.com/golang/go",
    "docs": "https://go.dev/doc/",
    "license": "BSD-3-Clause"
  },
  "example_vm_name": "my-go",
  "time": "10 minutes",
  "stack": [
    {
      "name": "Your Go app",
      "detail": "one binary, built from your repository, as app.service under systemd",
      "port": 8000,
      "data": "~/app → the current release in ~/releases"
    },
    {
      "name": "Go 1.27",
      "detail": "preinstalled; go.mod picks the version each build uses"
    },
    {
      "name": "git push deploys",
      "detail": "A pre-receive hook in ~/app.git builds, backs up, switches, and checks"
    },
    {
      "name": "SQLite",
      "detail": "modernc.org/sqlite (pure Go, no cgo) in new apps; settings in ~/app.env",
      "data": "~/data"
    },
    {
      "name": "Nightly backup",
      "detail": "app-backup.timer; data, settings, and the repository",
      "data": "~/backups"
    }
  ],
  "access": [
    {
      "url": "https://my-go.exe.xyz/",
      "who": "private",
      "what": "Your app, until you make it public"
    },
    {
      "url": "my-go.exe.xyz:app.git",
      "who": "private",
      "what": "`git push exe main` deploys"
    }
  ],
  "inputs": [
    {
      "name": "REPO",
      "description": "the GitHub repository to deploy, as owner/name or a URL, or `new` to start a fresh Go app"
    }
  ],
  "delivers": [
    "The app's binary answering on port 8000 as app.service, enabled to come back after a reboot.",
    "A ~/app.git repository whose pre-receive hook builds each push to main into a new release and rejects the push if the deploy fails.",
    "Settings and secrets in ~/app.env (mode 600), read by the build and by the server.",
    "Data in ~/data, backed up nightly by app-backup.timer, with the newest backup opened and checked in a scratch directory.",
    "With REPO=new, a net/http guestbook that saves messages to SQLite, with its templates and static files embedded in the binary."
  ],
  "deployable": true,
  "deploy": {
    "new_vm_url": "https://exe.dev/new?prompt=REPO%3D%3Cfill-this-in%3E%0A%0ASet%20up%20Go%20on%20this%20VM%20by%20following%20the%20exe.dev%20recipe%20at%20https%3A%2F%2Fexe.dev%2Fdeploy%2Fgo.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%20Go%20app%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-go.%20This%20VM%27s%20name%20is%20the%20output%20of%20%60hostname%60%3A%20use%20it%20wherever%20the%20recipe%20writes%20my-go.%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-%20The%20app%27s%20binary%20answering%20on%20port%208000%20as%20app.service%2C%20enabled%20to%20come%20back%20after%20a%20reboot.%0A-%20A%20~%2Fapp.git%20repository%20whose%20pre-receive%20hook%20builds%20each%20push%20to%20main%20into%20a%20new%20release%20and%20rejects%20the%20push%20if%20the%20deploy%20fails.%0A-%20Settings%20and%20secrets%20in%20~%2Fapp.env%20%28mode%20600%29%2C%20read%20by%20the%20build%20and%20by%20the%20server.%0A-%20Data%20in%20~%2Fdata%2C%20backed%20up%20nightly%20by%20app-backup.timer%2C%20with%20the%20newest%20backup%20opened%20and%20checked%20in%20a%20scratch%20directory.%0A-%20With%20REPO%3Dnew%2C%20a%20net%2Fhttp%20guestbook%20that%20saves%20messages%20to%20SQLite%2C%20with%20its%20templates%20and%20static%20files%20embedded%20in%20the%20binary.%0A%0ARead%20the%20whole%20recipe%2C%20including%20the%20closed%20details.%20Follow%20%22Set%20up%20by%0Ahand%22%20from%20%22Check%20Go%22%20to%20the%20end%20of%20it%2C%20as%20exedev%3B%20the%20walkthrough%0Aabove%20it%20is%20for%20the%20owner%20afterwards.%20Do%20not%20run%20Restore%20or%20Update%2C%20and%0Ado%20not%20reboot.%20Leave%20public%20sharing%20off.%0AREPO%20picks%20one%20path%20in%20%22Get%20the%20code%22%3A%20%60new%60%20starts%20a%20fresh%20app%3B%0Aanything%20else%20is%20a%20GitHub%20repository%20%28turn%20URLs%20into%20owner%2Fname%29.%20Never%0Acommit%20to%20a%20repository%20you%20cloned%3A%20its%20settings%20go%20in%20~%2Fapp.env.%20If%20a%0Aclone%20fails%2C%20follow%20the%20private-repository%20steps%2C%20and%20if%20that%20fails%0Atoo%2C%20give%20the%20owner%20the%20integration%20command%20and%20wait%20for%20them.%0AFor%20a%20cloned%20app%2C%20find%20its%20main%20package%20and%20set%20MAIN_PACKAGE%3B%20fill%0A~%2Fapp.env%20from%20its%20README%20or%20.env.example%3A%20make%20it%20listen%20on%20port%208000%2C%0Agenerate%20secrets%20with%20openssl%2C%20ask%20the%20owner%20for%20keys%20to%20outside%0Aservices%2C%20and%20never%20invent%20values.%20If%20a%20build%20fails%2C%20read%20its%20output%2C%0Afix%20what%20you%20can%20without%20editing%20the%20code%2C%20and%20redeploy%20with%0A~%2Fapp.git%2Fhooks%2Fpre-receive%20HEAD.%0AFinish%20with%20the%20app%27s%20URL%20and%20the%20git%20commands%20the%20owner%20runs%20on%20their%0Aown%20computer%2C%20from%20%22Deploy%20with%20git%20push%22%3A%20%60git%20clone%20-o%20exe%60%20for%20a%20new%0Aapp%2C%20%60git%20remote%20add%20exe%60%20for%20a%20GitHub%20repository%2C%20then%20%60git%20push%20exe%0Amain%60%20%28or%20their%20default%20branch%29.%0A%0AFinish%20with%20the%20URLs%20I%20should%20open.%0A",
    "terminal": "curl -fsSL https://exe.dev/deploy/go/prompt.txt | ssh exe.dev new --prompt=/dev/stdin",
    "prompt": "REPO=\u003cfill-this-in\u003e\n\nSet up Go on this VM by following the exe.dev recipe at https://exe.dev/deploy/go.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 Go app\nAsk me for any that still say \u003cfill-this-in\u003e before you need them.\n\nThe recipe's example VM name is my-go. This VM's name is the output of `hostname`: use it wherever the recipe writes my-go. 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- The app's binary answering on port 8000 as app.service, enabled to come back after a reboot.\n- A ~/app.git repository whose pre-receive hook builds each push to main into a new release and rejects the push if the deploy fails.\n- Settings and secrets in ~/app.env (mode 600), read by the build and by the server.\n- Data in ~/data, backed up nightly by app-backup.timer, with the newest backup opened and checked in a scratch directory.\n- With REPO=new, a net/http guestbook that saves messages to SQLite, with its templates and static files embedded in the binary.\n\nRead the whole recipe, including the closed details. Follow \"Set up by\nhand\" from \"Check Go\" to the end of it, as exedev; the walkthrough\nabove it is for the owner afterwards. Do not run Restore or Update, and\ndo not reboot. Leave public sharing off.\nREPO picks one path in \"Get the code\": `new` starts a fresh app;\nanything else is a GitHub repository (turn URLs into owner/name). Never\ncommit to a repository you cloned: its settings go in ~/app.env. If a\nclone fails, follow the private-repository steps, and if that fails\ntoo, give the owner the integration command and wait for them.\nFor a cloned app, find its main package and set MAIN_PACKAGE; fill\n~/app.env from its README or .env.example: make it listen on port 8000,\ngenerate secrets with openssl, ask the owner for keys to outside\nservices, and never invent values. If a build fails, read its output,\nfix what you can without editing the code, and redeploy with\n~/app.git/hooks/pre-receive HEAD.\nFinish with the app's URL and the git commands the owner runs on their\nown computer, from \"Deploy with git push\": `git clone -o exe` for a new\napp, `git remote add exe` for a GitHub repository, then `git push exe\nmain` (or their default branch).\n\nFinish with the URLs I should open.\n",
    "prompt_url": "https://exe.dev/deploy/go/prompt.txt"
  },
  "updated": "2026-10-31",
  "verified": "2026-10-31",
  "related": [
    "nodejs",
    "fastapi",
    "nextjs",
    "postgres",
    "dev-test-prod",
    "claude-code"
  ],
  "alternatives": [
    "Fly.io",
    "Render",
    "Railway",
    "Heroku"
  ]
}
