# How to Deploy Logto to Production: A Complete Self-Hosting Guide

> Deploy Logto to production with this comprehensive self-hosting guide. Learn to configure environment variables, run the Docker Compose stack with PostgreSQL, and set up TLS for secure access.

- Repository: [Logto/logto](https://github.com/logto-io/logto)
- Tags: how-to-guide
- Published: 2026-06-30

---

**Deploy Logto to production by configuring environment variables from the `.env.example` template, running the official [`docker-compose.yml`](https://github.com/logto-io/logto/blob/main/docker-compose.yml) stack with PostgreSQL, and terminating TLS at a reverse proxy.**

Logto is a self-hosted OIDC/OAuth 2.1 identity provider maintained in the `logto-io/logto` repository. This guide explains how to deploy Logto to production using the official containerization artifacts and configuration patterns found in the source code.

## Prerequisites and Host Preparation

Before deploying Logto to production, prepare a Linux host with Docker Engine 20.10+ and Docker Compose v2 installed. Create a dedicated non-root user for container execution to follow security best practices.

Open the following ports on your firewall:

- **3001** – Core public endpoint (OIDC/OAuth 2.1)
- **3002** – Core admin API and console
- **5432** – PostgreSQL database (internal or external)

## Configuring Production Environment Variables

Logto exposes all runtime configuration through environment variables parsed in [`packages/core/src/config.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/config.ts). Copy the example file to create your production configuration:

```bash
curl -sSL https://raw.githubusercontent.com/logto-io/logto/master/.env.example -o .env

```

Edit the `.env` file to set these critical production variables:

| Variable | Purpose | Production Value |
|----------|---------|------------------|
| `DB_URL` | PostgreSQL connection string | `postgres://logto:strong-password@db:5432/logto` |
| `ADMIN_URL` | Public URL for the admin console | `https://admin.example.com` |
| `ENDPOINT_URI` | Public Core URL for OIDC discovery | `https://auth.example.com` |
| `JWT_SECRET` | HMAC secret for token signing (≥32 bytes) | Randomly generated string |
| `SESSION_COOKIE_SECURE` | Enforce HTTPS-only cookies | `true` |
| `CORS_ALLOWED_ORIGINS` | Permitted origins for API requests | `https://app.example.com` |

The `LOGTO_DISABLE_DEVELOPER_MODE=true` setting disables development shortcuts and enables strict production validations.

## Starting the Logto Production Stack

The repository's [`docker-compose.yml`](https://github.com/logto-io/logto/blob/main/docker-compose.yml) orchestrates the Core server, PostgreSQL database, and automatic database migrations. From the repository root, execute:

```bash
docker compose -p logto -f docker-compose.yml pull
docker compose -p logto -f docker-compose.yml up -d

```

This compose configuration performs the following actions automatically:

1. **Database initialization** – Creates a persistent Docker volume (`logto-db-data`) for PostgreSQL
2. **Schema migrations** – Runs `pnpm cli db migrate` from `packages/schemas/migrations/` on startup
3. **Server startup** – Executes `pnpm prepack && pnpm start:prod` inside the `node:20-alpine` container
4. **Service exposure** – Binds port 3001 for public OIDC endpoints and 3002 for the admin API

The migrations are idempotent, allowing safe restarts and updates without manual intervention.

## Enabling TLS and Reverse Proxy

While Logto containers expose plain HTTP internally, production deployments must terminate TLS at a reverse proxy. Configure Nginx to forward traffic to the Core containers:

```nginx
server {
  listen 443 ssl;
  server_name auth.example.com;

  ssl_certificate     /etc/ssl/certs/example.com.crt;
  ssl_certificate_key /etc/ssl/private/example.com.key;

  location / {
    proxy_pass http://localhost:3001;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
  }
}

```

Alternatively, use Traefik or Caddy for automatic Let's Encrypt certificate management. Ensure `ENDPOINT_URI` and `ADMIN_URL` environment variables reflect your public HTTPS URLs.

## Verifying the Production Deployment

Confirm successful deployment by checking the OpenID Connect discovery endpoint:

```bash
curl https://auth.example.com/.well-known/openid-configuration

```

This should return a JSON document containing the OIDC configuration. Access the admin console at your configured `ADMIN_URL` and authenticate using the initial credentials created during the database seeding phase.

Verify database migrations completed by inspecting the schema version in the PostgreSQL instance; migration scripts reside in `packages/schemas/migrations/` and apply automatically when the container starts.

## Scaling and Maintenance

**Horizontal scaling** requires only running additional Core containers behind the same reverse proxy. All instances connect to the shared PostgreSQL database and maintain stateless operation.

**Zero-downtime upgrades** follow this workflow:

1. Update the image tag in [`docker-compose.yml`](https://github.com/logto-io/logto/blob/main/docker-compose.yml)
2. Run `docker compose pull && docker compose up -d`
3. The new container executes migrations automatically before accepting traffic

For custom deployments, build a production image using the repository `Dockerfile`:

```dockerfile
FROM logtoio/logto:latest
COPY .env /app/.env
RUN pnpm prepack
EXPOSE 3001 3002
CMD ["pnpm", "start:prod"]

```

## Summary

- **Configuration** – Set production secrets in `.env` based on the `.env.example` template, ensuring `JWT_SECRET` exceeds 32 bytes and `SESSION_COOKIE_SECURE` is enabled
- **Orchestration** – Use the official [`docker-compose.yml`](https://github.com/logto-io/logto/blob/main/docker-compose.yml) to run PostgreSQL and Logto Core with automatic migrations from `packages/schemas/migrations/`
- **Security** – Terminate TLS at a reverse proxy and restrict `CORS_ALLOWED_ORIGINS` to known application domains
- **Entry point** – The Core server initializes in [`packages/core/src/app.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/app.ts), loading configurations from [`packages/core/src/config.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/config.ts) before binding to ports 3001 and 3002

## Frequently Asked Questions

### What are the minimum system requirements for running Logto in production?

Logto requires a host capable of running Docker containers with at least 2GB RAM and 20GB storage for the initial deployment. PostgreSQL 14+ is required for the database layer, though the [`docker-compose.yml`](https://github.com/logto-io/logto/blob/main/docker-compose.yml) provides a preconfigured instance. CPU requirements scale with authentication volume; a single Core container handles thousands of requests per minute on standard cloud instance sizes.

### How do I handle database migrations when upgrading Logto?

Database migrations run automatically when the Core container starts. The migration scripts in `packages/schemas/migrations/` execute sequentially via `pnpm cli db migrate` before the HTTP server binds to ports 3001 and 3002. Because migrations are idempotent, you can safely perform zero-downtime upgrades by bringing up new containers before terminating old ones.

### Can I run Logto without Docker in production?

While possible by executing `pnpm start:prod` directly on a Node.js 20+ host, the repository strongly recommends containerized deployment. The `Dockerfile` and [`docker-compose.yml`](https://github.com/logto-io/logto/blob/main/docker-compose.yml) handle dependency resolution, environment isolation, and migration sequencing that manual deployments must replicate. Running bare-metal requires manually managing PostgreSQL connections and the build process defined in `pnpm prepack`.

### How do I add social connectors like Google in a production deployment?

Install connectors at runtime without rebuilding the image. Execute `docker exec -it logto-core pnpm add @logto/connector-google` inside the running container, then register the connector via the admin API at port 3002. The connector configuration persists in PostgreSQL, making it available across container restarts and scalable to multiple Core instances without code changes.