# Deploy Go apps on Vercel using Docker

**Author:** Anshuman Bhardwaj

---

Go apps usually deploy cleanly on Vercel with the native Go preset: a `go.mod`, a supported entrypoint, and an HTTP server that listens on `PORT`. But not every Go project fits that shape. Some ship their own startup scripts, depend on external binaries, or are already published as a container image you'd rather run as-is.

Docker often fits those cases better. This guide shows how to deploy a Dockerized Go application on Vercel using [Memos](https://usememos.com/docs/deploy/docker), an open-source note-taking app, as an example. Memos runs as a Go server from the `neosmemo/memos:stable` image and stores durable data in Postgres.

## When to run Go using Docker

Use Vercel's native [Go Runtime](https://vercel.com/docs/functions/runtimes/go) when your app is a standard Go HTTP server with a root `go.mod`, a supported entrypoint like `main.go`, and code you can adapt to listen on `PORT`.

Use Docker when the app:

- Already ships as a maintained OCI image.
  
- Has a custom entrypoint or runtime behavior you do not want to rewrite.
  
- Needs operating-system packages, CA bundles, native binaries, or bundled assets.
  
- Uses a framework or app layout outside Vercel's Go preset assumptions.
  
- Should run locally and in production from the same image.
  
- Is an upstream open-source app, like Memos, where you want to configure rather than fork.
  

Docker on Vercel still runs as a Vercel Function. The container must serve HTTP, stay stateless, use external storage for durable data, and respect Function limits. Vercel builds the image, stores it in [Vercel Container Registry](https://vercel.com/docs/container-registry), and routes traffic to it.

## How to run Docker on Vercel

Vercel supports container images through `Dockerfile.vercel` or `Containerfile.vercel`. In a Services project, declare the container service in `vercel.json` and expose it with a rewrite.

Memos is a useful example because it is a Go app, already has an official Docker image, and can switch from local SQLite to Postgres through environment variables.

### Add Vercel Dockerfile

First, create a `Dockerfile.vercel` that only runs the upstream Memos image on a port that Vercel can reach:

`FROM neosmemo/memos:stable ENV MEMOS_ADDR=0.0.0.0 \ MEMOS_PORT=5230 \ MEMOS_DATA=/tmp/memos RUN mkdir -p /tmp/memos && chmod 1777 /tmp/memos EXPOSE 5230`

Here is what each part does:

- `FROM neosmemo/memos:stable` uses the official Memos image instead of rebuilding the Go app from source.
  
- `MEMOS_ADDR=0.0.0.0` makes the server accept traffic from outside the container.
  
- `MEMOS_PORT=5230` keeps Memos on its normal unprivileged port. The image runs as a non-root user, so binding to port `80` can fail.
  
- `MEMOS_DATA=/tmp/memos` gives Memos a writable local directory. On Vercel this is ephemeral, so it must not be the system of record.
  
- `EXPOSE 5230` documents the container port.
  

Also, set the Vercel project environment variable `PORT=5230`. Vercel routes to port `80` by default unless `PORT` is configured. Memos itself reads `MEMOS_PORT`, while Vercel's container router reads `PORT`, so both values matter.

`vercel env add PORT production --no-sensitive vercel env add PORT preview --no-sensitive vercel env add PORT development`

### Add Postgres from the Vercel Marketplace

The Memos app requires a persistent database service. SQLite is not suitable for durable production data on Vercel Functions. Use a Postgres database integration such as Neon instead. This will provide the data and file storage for the app.

From a linked Vercel project, you can discover and install Neon from the Marketplace with the Vercel CLI.

Install Neon, create a resource, and connect it to the current project:

`vercel integration add neon`

### Connect Memos to Postgres

Memos needs these settings to use Postgres:

`MEMOS_DRIVER=postgres`

Add `MEMOS_DRIVER=postgres` to the Dockerfile:

`ENV MEMOS_ADDR=0.0.0.0 \ MEMOS_PORT=5230 \ MEMOS_DRIVER=postgres \ MEMOS_DATA=/tmp/memos`

You could manually copy Neon's connection string into a new Vercel environment variable named `MEMOS_DSN`, but that duplicates a secret. To avoid duplicating the secret, derive `MEMOS_DSN` at startup.

Add this `ENTRYPOINT` line to `Dockerfile.vercel`:

`ENTRYPOINT ["sh", "-c", "export MEMOS_DSN=\"${MEMOS_DSN:-${DATABASE_URL_UNPOOLED:-$DATABASE_URL}}\"; for bin in /usr/local/memos/memos /usr/local/bin/memos /usr/bin/memos /memos; do if [ -x \"$bin\" ]; then exec \"$bin\"; fi; done; echo \"memos binary not found\" >&2; exit 127"]` This line does three jobs: - If `MEMOS_DSN` is already set, Memos uses it.    - Otherwise, it maps Neon's `DATABASE_URL_UNPOOLED` to `MEMOS_DSN`, falling back to `DATABASE_URL`.    - It starts the Memos binary from the upstream image.    The final `Dockerfile.vercel` looks like this: `FROM neosmemo/memos:stable # Vercel's container router is configured with PORT=5230 for this project. # Memos runs as a non-root user, so it cannot bind to privileged port 80. # Memos stores durable app data in Neon. The startup command maps Neon-provided # database URLs to Memos' MEMOS_DSN when MEMOS_DSN is not set directly. ENV MEMOS_ADDR=0.0.0.0 \ MEMOS_PORT=5230 \ MEMOS_DRIVER=postgres \ MEMOS_DATA=/tmp/memos RUN mkdir -p /tmp/memos && chmod 1777 /tmp/memos EXPOSE 5230 ENTRYPOINT ["sh", "-c", "export MEMOS_DSN=\"${MEMOS_DSN:-${DATABASE_URL_UNPOOLED:-$DATABASE_URL}}\"; for bin in /usr/local/memos/memos /usr/local/bin/memos /usr/bin/memos /memos; do if [ -x \"$bin\" ]; then exec \"$bin\"; fi; done; echo \"memos binary not found\" >&2; exit 127"]` ### Add a Services config (optional) [Vercel Services](https://vercel.com/docs/services) lets you deploy one or more services in a single Vercel project. If you later add a frontend, you can route only a path prefix to the Go container and send everything else to the frontend service:

`{ "services": { "web": { "root": "apps/web", "framework": "nextjs" }, "api": { "root": "apps/api", "runtime": "container", "entrypoint": "Dockerfile.vercel" } }, "rewrites": [{ "source": "/api/(.*)", "destination": { "service": "api" } }, { "source": "/(.*)", "destination": { "service": "web" } } ] }` ### Deploy to Vercel From the project root: `vercel deploy --prod` During deployment, Vercel builds the image, pushes it to Vercel Container Registry, creates a Function for the container service, and aliases the production URL. After deploy, verify the app: `curl -I https://your-project.vercel.app vercel inspect https://your-project.vercel.app vercel logs https://your-project.vercel.app --since 10m --expand` ## Troubleshooting ### The deployment is ready, but the site returns 404 **Cause**: The container built, but no public route points to it. In Services mode, a service is private until a top-level rewrite exposes it. **Fix**: Add rewrites in the `vercel.json`: `{ "rewrites": [ { "source": "/(.*)", "destination": { "service": "memos" } } ] }` Also, confirm the project framework is set to Services when using `services` in `vercel.json`. ### The container returns 500 **Cause**: The app is trying to listen on port `80` while running as a non-root user. Memos runs as a non-root user, so binding to privileged ports can fail. **Fix**: Set Memos to listen on an unprivileged port: `ENV MEMOS_PORT=5230 EXPOSE 5230` Then set the Vercel project environment variable: `PORT=5230` Vercel uses `PORT` to decide where to route traffic inside the container. Memos uses `MEMOS_PORT` to decide where the app listens. You need both. ### Build logs say no `functions` or `static` directory **Cause**: In the Services and container path, this warning can appear even when Vercel produced the container-backed Function. **Fix**: Inspect the deployment instead of relying only on that warning. The app is deployed as a container image. `vercel inspect <deployment-url>` ## Related resources - [Vercel Container Images](https://vercel.com/docs/functions/container-images)
  
- [Vercel Services](https://vercel.com/docs/services)
  
- [Vercel Go Runtime](https://vercel.com/docs/functions/runtimes/go)
  
- [Memos Docker docs](https://usememos.com/docs/deploy/docker)
  
- [Neon and Vercel manual setup](https://neon.com/docs/guides/vercel-manual)

---

[View full KB sitemap](/kb/sitemap.md)
