Skip to content

Docker Compose

This guide covers deploying the Expose server using Docker Compose.

Directory Structure

/opt/expose/
├── deploy/
│ ├── docker-compose.yml
│ ├── .env
│ └── server/
│ └── Dockerfile
└── migrations/
└── 001_initial_schema.sql

docker-compose.yml

The default docker-compose.yml is located in the deploy/ directory:

services:
expose-server:
build:
context: ..
dockerfile: deploy/server/Dockerfile
restart: always
environment:
- DATABASE_URL=${DATABASE_URL}
- JWT_SECRET=${JWT_SECRET}
- BASE_DOMAIN=${BASE_DOMAIN}
- WEBHOOK_SECRET=${WEBHOOK_SECRET}
- RUST_LOG=info
depends_on:
postgres:
condition: service_healthy
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks:
- web
- backend
- expose_user_apps
labels:
# API routes
- "traefik.enable=true"
- "traefik.http.routers.expose-api.rule=Host(`api.${BASE_DOMAIN}`)"
- "traefik.http.routers.expose-api.entrypoints=websecure"
- "traefik.http.routers.expose-api.tls.certresolver=letsencrypt"
# Tunnel routes (wildcard)
- "traefik.http.routers.expose-tunnel.rule=HostRegexp(`{subdomain:[a-z0-9-]+}.expose.${BASE_DOMAIN}`)"
- "traefik.http.routers.expose-tunnel.entrypoints=websecure"
- "traefik.http.routers.expose-tunnel.tls.certresolver=letsencrypt"
- "traefik.http.routers.expose-tunnel.tls.domains[0].main=expose.${BASE_DOMAIN}"
- "traefik.http.routers.expose-tunnel.tls.domains[0].sans=*.expose.${BASE_DOMAIN}"
# Hosted routes (wildcard)
- "traefik.http.routers.expose-hosted.rule=HostRegexp(`{subdomain:[a-z0-9-]+}.hosted.${BASE_DOMAIN}`)"
- "traefik.http.routers.expose-hosted.entrypoints=websecure"
- "traefik.http.routers.expose-hosted.tls.certresolver=letsencrypt"
- "traefik.http.routers.expose-hosted.tls.domains[0].main=hosted.${BASE_DOMAIN}"
- "traefik.http.routers.expose-hosted.tls.domains[0].sans=*.hosted.${BASE_DOMAIN}"
- "traefik.docker.network=web"
postgres:
image: postgres:16-alpine
restart: always
environment:
- POSTGRES_USER=expose
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=expose
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- backend
healthcheck:
test: ["CMD-SHELL", "pg_isready -U expose"]
interval: 5s
timeout: 5s
retries: 5
registry:
image: registry:2
restart: always
volumes:
- registry_data:/var/lib/registry
networks:
- backend
volumes:
postgres_data:
registry_data:
networks:
web:
external: true
backend:
internal: true
expose_user_apps:
name: expose_user_apps

Deploying

1. Clone the Repository

Terminal window
git clone https://github.com/basique-industrie/expose /opt/expose
cd /opt/expose/deploy

2. Configure Environment

Terminal window
cp .env.example .env
nano .env

3. Create Networks

Terminal window
docker network create web
docker network create expose_user_apps

4. Start Services

Terminal window
docker compose up -d

5. Check Status

Terminal window
# View running containers
docker compose ps
# View logs
docker compose logs -f expose-server
# Check health
curl https://api.yourdomain.com/health

Updating

To update the server:

Terminal window
cd /opt/expose
git pull
cd deploy
docker compose build expose-server
docker compose up -d expose-server

Scaling

For high availability, you can run multiple instances behind a load balancer. Note that tunnel connections are stateful, so sticky sessions are recommended.

Backup

Database

Terminal window
# Backup
docker compose exec postgres pg_dump -U expose expose > backup.sql
# Restore
cat backup.sql | docker compose exec -T postgres psql -U expose expose

Registry

The registry data is stored in a Docker volume. Back it up:

Terminal window
docker run --rm -v expose_registry_data:/data -v $(pwd):/backup alpine tar czf /backup/registry-backup.tar.gz -C /data .