---
title: "Deploy with Docker Compose | FoxAuth"
description: "A production-leaning Compose file, with pinned tags, a database you back up, and a TLS proxy in front."
canonical: "https://foxauth.dev/docs/deploy/docker-compose/"
lastmod: "2026-10-07T23:49:28+03:00"
---
Deploy with Docker Compose

The Compose file in Get started is built for a first look. Four
changes make it something you can leave running.

The fileSection titled “The file”

services:

setup:

image: ghcr.io/redfox-soft/oauth-server-ts:<version>

command: ['sh', '-c', 'bun run db:setup && bun run db:migrate']

env_file: .env

restart: 'no'

oauth:

image: ghcr.io/redfox-soft/oauth-server-ts:<version>

ports:

- '127.0.0.1:3000:3000'

env_file: .env

depends_on:

setup:

condition: service_completed_successfully

restart: unless-stopped

with a .env beside it:

ISSUER=https://auth.example.com

MONGODB_URI=mongodb+srv://user:password@cluster.example.mongodb.net

DATABASE_NAME=OAuth

What changed, and whySection titled “What changed, and why”

Pin the image tag. Images are published to ghcr.io/redfox-soft/oauth-server-ts on every
release, tagged with the version and with latest. latest means a docker compose pull can move
you across a release you have not read the changelog for. Name the version instead, and change it
on purpose. That is also what turns the upgrade into a reviewable diff.
The published tags are listed on the
GitHub releases page and on the
container package page.

Verify the image once per version you adopt. Every release is signed over its digest and carries
an SBOM and a build provenance attestation, so you can check the tag you just pinned instead of
trusting it:

Terminal window

cosign verify \

--certificate-oidc-issuer https://token.actions.githubusercontent.com \

--certificate-identity-regexp '^https://github\.com/RedFox-Soft/OAuth-server\.ts/\.github/workflows/release\.yml@refs/tags/v' \

ghcr.io/redfox-soft/oauth-server-ts:<version>

It fits naturally into the same change that moves the pin, since that is usually the moment you are
already reading the changelog. What is attached and how to read the SBOM.

Use a database you back up. The Get started file runs mongo:8 on a local named volume, which
is convenient and has no backups. Either point MONGODB_URI at a managed instance such as
MongoDB Atlas, or keep the local mongo service and treat the
volume as the production data it is:

mongo:

image: mongo:8

volumes:

- mongo-data:/data/db

healthcheck:

test:

['CMD', 'mongosh', '--quiet', '--eval', 'db.runCommand({ ping: 1 }).ok']

interval: 5s

timeout: 5s

retries: 12

restart: unless-stopped

with depends_on: { mongo: { condition: service_healthy } } on the setup service, and a scheduled
mongodump against the volume. Everything a deployment cannot lose is in that database: end-user
accounts, clients, the audit trail, and the signing keys.

To run PostgreSQL instead, swap the service for postgres:17, put POSTGRES_URL in the .env in
place of MONGODB_URI and DATABASE_NAME, and change the setup command to
['bun', 'run', 'db:setup:pg']. Set one connection string, never both. Given both, the server
refuses to start instead of picking one. PostgreSQL has the details.

restart: unless-stopped on the server, restart: 'no' on setup. The setup service is a
one-shot that must exit; restarting it would loop. The server should come back after a host reboot
or a crash — remember that a Machine which cannot reach the datastore exits at boot instead of
serving, so a restart policy is how it recovers once the database comes back.

Bind the port to loopback and put a proxy in front. '127.0.0.1:3000:3000' keeps the plaintext
port off the network. TLS terminates in the proxy, and ISSUER is the public https URL.
Behind a reverse proxy has the nginx and Caddy blocks and the one
setting you must get right there.

ISSUER is final before the first start

The setup service derives the console client’s redirect URI from ISSUER the first time it runs,
and a later change is not repaired by re-running it. Put the final public URL in .env before the
first docker compose up.

UpgradingSection titled “Upgrading”

Change the tag and bring it up again:

Terminal window

docker compose pull

docker compose up -d

setup re-runs on every up, which is intended: both of its commands are safe to repeat, and
together they apply a new release’s collections, indexes, seed changes and schema migrations. The
server waits for it to complete successfully before starting, and would refuse to start without the
migrations anyway. When the release notes name a migration that cannot be undone, take a backup
before the up. The full checklist, including what a restart does and does not carry over, is in
Upgrading.

PreviousDeploy to Fly.ioNextDeploy on Kubernetes
