---
title: Deploy Go apps on Vercel using Docker
description: Deploy an existing Dockerized Go app to Vercel using Memos as a real-world example, with Neon Postgres for durable data.
url: "https://vercel.com/kb/guide/deploy-go-using-docker-vercel"
published: 2026-07-21
last_updated: 2026-10-29
authors: Anshuman Bhardwaj
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

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 a root `Dockerfile` or `Containerfile`. Add a `Dockerfile` at the project root and Vercel builds it, stores the image, and routes all traffic to it, so this app needs no `vercel.json`.

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 a Dockerfile

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

```dockerfile
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.
  

On a new project, Vercel detects the root `Dockerfile`, selects the Container preset, and routes all traffic to the image. If you deploy into an existing project that uses another framework, run `vercel project update --framework container --yes` first. A `Dockerfile.vercel` or `Containerfile.vercel` also works and takes precedence over a detected framework.

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.

```bash
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:

```bash
vercel integration add neon
```

### Connect Memos to Postgres

Memos needs `MEMOS_DRIVER=postgres` to use Postgres. Add it to the Dockerfile:

```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`:

```dockerfile
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` looks like this:

```dockerfile
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:

```json
{
  "services": {
    "web": {
      "root": "apps/web",
      "framework": "nextjs"
    },
    "api": {
      "root": "apps/api",
      "runtime": "container",
      "entrypoint": "Dockerfile"
    }
  },
  "rewrites": [
    { "source": "/api/(.*)", "destination": { "service": "api" } },
    { "source": "/(.*)", "destination": { "service": "web" } }
  ]
}
```

### Deploy to Vercel

From the project root:

```bash
vercel deploy --prod
```

During deployment, Vercel builds the image, pushes it to Vercel Container Registry, creates a Function for the container, and aliases the production URL.

After deploy, verify the app:

```bash
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. This happens when the project uses another framework preset instead of Container, or, with the optional Services config, when no top-level rewrite exposes the container service.

**Fix**: Set the project framework to Container and redeploy:

```bash
vercel project update --framework container --yes
```

If you use the optional Services config, set the project framework to Services instead and add a top-level rewrite in `vercel.json` that sends traffic to the container service.

### 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:

```dockerfile
ENV MEMOS_PORT=5230
EXPOSE 5230
```

Then set the Vercel project environment variable:

```bash
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 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.

```bash
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)
  

## FAQ

### How long can a Dockerized Go app run?

Container image services inherit [Vercel Function limits](https://vercel.com/docs/functions/limitations). They are designed for HTTP request handling, not permanent background daemons.

### Can I use SQLite inside the container?

Not for durable production data. Vercel containers are stateless and can scale down or be replaced. Use a Postgres database integration, such as Neon, for persistent data.

### Can I deploy a frontend and Dockerized Go backend together?

Yes. Use [Vercel Services](https://vercel.com/docs/services) with one frontend service and one container service, then route `/api/*` or another path to the backend.

### Where should I put secrets?

Use Vercel environment variables or integration-injected variables. Do not put database URLs, API keys, or credentials in the Dockerfile or repository.

### Can I use WebSockets with a Dockerized Go app?

Vercel supports WebSockets for Vercel Functions on Fluid compute, including container-image Functions, with current platform limits. For long-lived connections, design clients to reconnect.

## Related Documentation

- [Vercel Container Registry](https://vercel.com/docs/container-registry)
- [Vercel Functions](/docs/functions)

## More Vercel Container Registry guides

- [Running Docker on Vercel](/kb/guide/docker): Run Docker on Vercel for HTTP apps and background workers, store images in Vercel Container Registry, and test containers with Vercel Sandbox.
- [Running Docker on Vercel vs Render](/kb/guide/docker-on-vercel-vs-render): Compare how Vercel and Render run Docker workloads, including deployment model, scaling, image sources, state, and networking.
- [Migrate from Render to Vercel](/kb/guide/migrate-render-to-vercel): Migrate from Render to Vercel: map web services, cron jobs, and containers to their equivalents, and handle what doesn't move.