---
updatedAt: 2026-01-05T20:07:29.000Z
agentTools:
  projectIndex: https://synaccess.readme.io/llms.txt
---

# First-Time Setup & Deployment (Docker Compose)

This page walks you through deploying the application using **Docker Compose** on a fresh host.
You’ve already pulled the image (see previous page).

## Prerequisites

* Docker Engine / CLI **28.1.1** (or compatible)
* Docker Compose plugin **v2.x**
* Image already pulled from `synaccess.azurecr.io`
* A deployment folder (example path used below: `/opt/syn-cloud`)

*Check your versions:*

```bash
docker --version
docker compose version
```

***

## 1) Create the deployment folder

```bash
sudo mkdir -p /opt/syn-cloud/{certs}
cd /opt/syn-cloud
```

* Put any TLS/CA files you need under `./certs/` (host) — they’ll appear in the container at `/certs/...`..

***

## 2a) Create the files for Syn Cloud App only (database not included)

Create **`docker-compose.yml`** in `/opt/syn-cloud`:

```yaml
version: "3.9"
services:
  syn-cloud:
    # Default to 'latest' unless TAG is set in .env
    image: synaccess.azurecr.io/syn-cloud:${TAG:-latest}
    container_name: syn-cloud
    env_file:
      - .env

    # Uncomment/leave as-is to make TLS material or other read-only assets
    # available inside the container. For example, mount database CA certificates
    # or HTTPS cert/key pairs and reference the in-container paths from your .env
    # (e.g., TLS_CERT=/certs/fullchain.pem).
    volumes:
      - ./certs:/certs:ro

    # Publishes the app on the same port configured in .env.
    # When PORT=443 and CONNECTION_SECURITY=tls the container will terminate TLS.
    ports:
      - "${PORT:-80}:${PORT:-80}"

    restart: unless-stopped

    # Always check for a newer image on 'up' (useful when tracking 'latest')
    # pull_policy: always
```

Create **`.env`** next to it (copy/paste then edit values):

```env
# These values are consumed by docker-compose.yml and the application at runtime.

# -----------------------------------------------------------------------------
# Database connectivity
# -----------------------------------------------------------------------------
# Full PostgreSQL connection URI. Include credentials and database name.
# Example for managed Postgres: postgres://user:password@host:5432/database
DB_CONNECTION_STRING=postgres://username:password@database-host:5432/database-name

# Connection pool sizing. Keep small for low-resource deployments.
DB_MIN_CONNECTIONS=2
DB_MAX_CONNECTIONS=40

# TLS options (optional). Set DB_SSL_MODE to one of: disable, prefer, require, verify-ca, verify-full.
# When supplying certificate paths, mount them via docker-compose (see volumes example) and
# point these variables at the in-container path (e.g. /certs/root.crt). You can also
# inline PEM content by escaping newlines with \\n 
DB_SSL_MODE=
DB_SSL_ROOT_CERT=
DB_SSL_CERT=
DB_SSL_KEY=
# Set to false ONLY when you must skip certificate validation (not recommended).
DB_SSL_REJECT_UNAUTHORIZED=

# -----------------------------------------------------------------------------
# Application runtime
# -----------------------------------------------------------------------------
# The public hostname for the deployment (used when generating links/emails).
HOST=localhost

# Listening port inside the container AND host mapping. Ensure docker-compose ports section matches.
# Common values: 80 for HTTP, 443 when running TLS directly, 3000 for alternate ports.
PORT=80

# Bind address used by the web server; :: listens on all IPv4/IPv6 addresses.
BIND_HOST=0.0.0.0

# Optional override for the externally reachable URL. Leave blank to auto-generate from HOST/PORT/CONNECTION_SECURITY.
HOST_URL=

# Choose none or tls. When tls is selected, provide TLS_CERT and TLS_KEY.
CONNECTION_SECURITY=none

# When true AND running TLS, the app will redirect HTTP (80) to HTTPS (443) inside the container.
FORWARD_PORT_80_TO_443=false

# TLS certificate and key can be either inline PEM (escape newlines with \\n) or filesystem paths mounted into the container.
TLS_CERT=
TLS_KEY=

# Comma-separated list or wildcard for allowed CORS origins. Use * for testing only.
CORS_ORIGIN=

# -----------------------------------------------------------------------------
# Image pinning (optional)
# -----------------------------------------------------------------------------
# Leave TAG unset to track 'latest'. To pin/revert to a specific version, set TAG here.
# TAG=2025.10.02
```

> **Tip:** Keep a backup before edits:
>
> ```bash
> cp .env .env.backup.$(date +%Y%m%d-%H%M%S)
> ```

<br />

## 2b) Create the files for Syn Cloud App and Database

Create **`docker-compose.yml`** in `/opt/syn-cloud`

```yaml

version: "3.9"

services:
  timescaledb:
    image: timescale/timescaledb:2.13.1-pg15
    container_name: ${COMPOSE_PROJECT_NAME:-syn-cloud}-timescaledb
    restart: unless-stopped
    env_file:
      - .env
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
      PGDATA: ${PGDATA:-/var/lib/postgresql/data}
      TIMESCALEDB_TELEMETRY: ${TIMESCALEDB_TELEMETRY:-off}
    ports:
      - "${POSTGRES_PORT:-5432}:5432"
    volumes:
      - timescaledb-data:${PGDATA:-/var/lib/postgresql/data}
      - ./initdb:/docker-entrypoint-initdb.d
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 5

  syn-cloud:
    image: ${SYN_CLOUD_IMAGE:-synaccess.azurecr.io/syn-cloud:latest}
    container_name: ${COMPOSE_PROJECT_NAME:-syn-cloud}-app
    restart: unless-stopped
    env_file:
      - .env
    depends_on:
      - timescaledb
    # Uncomment to mount TLS material or other read-only assets into the
    # container (e.g., database CA certificates referenced by DB_SSL_* env vars).
    # setup-syn-cloud.sh automatically creates docker-compose.override.yml that
    # binds ./certs for Let's Encrypt deployments.
    # volumes:
    #   - ./certs:/certs:ro
    ports:
      - "${PORT:-80}:${PORT:-80}"

volumes:
  timescaledb-data:
    driver: local

```

Create **`.env`** next to it (copy/paste then edit values)

```
# These values are consumed by docker-compose.yml and the application at runtime.

# -----------------------------------------------------------------------------
# Docker Compose configuration
# -----------------------------------------------------------------------------
# Optional project name override used by docker-compose to prefix resources.
COMPOSE_PROJECT_NAME=syn-cloud

# -----------------------------------------------------------------------------
# Database bootstrap
# -----------------------------------------------------------------------------
# Credentials and database name that will be provisioned inside PostgreSQL.
POSTGRES_USER=syncloud
POSTGRES_PASSWORD=change_me
POSTGRES_DB=syncloud

# Network binding for the PostgreSQL container. The host port is what clients on
# your machine will connect to. The container will always listen on 5432.
POSTGRES_PORT=5432
POSTGRES_HOST=timescaledb

# Location inside the container where PostgreSQL stores its data. If you change
# this, also update the volume mount in docker-compose.yml.
PGDATA=/var/lib/postgresql/data/pgdata

# Disable anonymous telemetry collection from the TimescaleDB image by default.
TIMESCALEDB_TELEMETRY=off

# -----------------------------------------------------------------------------
# Database connectivity for the application
# -----------------------------------------------------------------------------
# Full PostgreSQL connection URI. Include credentials and database name. The
# hostname should match the service name defined in docker-compose.yml.
DB_CONNECTION_STRING=postgres://syncloud:change_me@timescaledb:5432/syncloud

# Connection pool sizing. Keep small for low-resource deployments.
DB_MIN_CONNECTIONS=2
DB_MAX_CONNECTIONS=40

# TLS options (optional). Set DB_SSL_MODE to one of: disable, prefer, require,
# verify-ca, verify-full. When supplying certificate paths, mount them via
# docker-compose (see volumes example) and point these variables at the
# in-container path (e.g. /certs/root.crt). You can also inline PEM content by
# escaping newlines with \\n
DB_SSL_MODE=
DB_SSL_ROOT_CERT=
DB_SSL_CERT=
DB_SSL_KEY=
# Set to false ONLY when you must skip certificate validation (not recommended).
DB_SSL_REJECT_UNAUTHORIZED=

# -----------------------------------------------------------------------------
# Application runtime
# -----------------------------------------------------------------------------
# The public hostname for the deployment (used when generating links/emails).
HOST=localhost

# Listening port inside the container AND host mapping. Ensure docker-compose
# ports section matches. Common values: 80 for HTTP, 443 when running TLS
# directly, 3000 for alternate ports.
PORT=80

# Bind address used by the web server; :: listens on all IPv4/IPv6 addresses.
BIND_HOST=0.0.0.0

# Optional override for the externally reachable URL. Leave blank to
# auto-generate from HOST/PORT/CONNECTION_SECURITY.
HOST_URL=

# Choose none or tls. When tls is selected, provide TLS_CERT and TLS_KEY.
CONNECTION_SECURITY=none

# When true AND running TLS, the app will redirect HTTP (80) to HTTPS (443)
# inside the container.
FORWARD_PORT_80_TO_443=false

# TLS certificate and key can be either inline PEM (escape newlines with \\n) or
# filesystem paths mounted into the container.
TLS_CERT=
TLS_KEY=

# Comma-separated list or wildcard for allowed CORS origins. Use * for testing
# only.
CORS_ORIGIN=

# -----------------------------------------------------------------------------
# Application image (optional overrides)
# -----------------------------------------------------------------------------
# Default Syn Cloud image to deploy with docker compose. Change this to point at
# a different registry, tag, or locally built image when needed.
SYN_CLOUD_IMAGE=synaccess.azurecr.io/syn-cloud:latest

```

<br />

***

<br />

<br />

## 3) Login (one-time per host)

```bash
docker login synaccess.azurecr.io -u <TOKEN_USER> -p '<TOKEN_PASSWORD>'
```

Expected output: `Login Succeeded`.

***

## 4) Start the application

```bash
docker compose up -d
```

Compose will create (or recreate) the container and start it with the variables from `.env`.

***

## 5) Verify

```bash
docker compose ps
docker compose logs --tail=200 syn-cloud
curl -I http://127.0.0.1:${PORT:-80} || true
```

* You should see `Up` in `docker compose ps`.
* Adjust the port if you changed `PORT` in `.env`.

***

## 6) Initial in-app setup (first login)

After the container is running and you can reach the URL, complete these one-time steps inside Syn Cloud.

1. **Open your Syn Cloud URL**
   Visit `https://<your-hostname>` (or `http://<your-hostname>:<port>` if not using TLS).

2. **Go to *Deployment Settings***
   On first boot, the app should direct users to /deployment-settings.

3. **Create the Super Admin account**
   * **Username** (or email)
   * **Email address**
   * **Strong password**
   * **Save**
   > This Super Admin account is the primary administrator. Its **email/username and password are used later when pairing PDUs** to your cloud.

4. **Configure SMTP (email)**
   * **SMTP host/port** (587 STARTTLS or 465 SMTPS)
   * **Username / password** (service account recommended)
   * **Encryption** (STARTTLS or SSL/TLS)
   * **Save** and **Send test email**
   > SMTP enables **email verification**, **password resets**, and user invitations. Without SMTP, users won’t be able to complete email-based flows.

5. **Confirm deployment details**
   On **Deployment Settings**, verify the displayed **Deployment URL** and environment details match your `.env` (for example `HOST`, `PORT`, and TLS settings).

When finished, continue with pairing PDUs and inviting users.

## 7) Database Connectivity Schemes

The app reads DB settings from `.env` and supports three common patterns:

| Scenario                      | How to configure in `.env`                                                                                                                                   | When to use                                                                    |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ |
| **Username + password**       | `DB_CONNECTION_STRING=postgres://username:password@db-host:5432/database` (leave TLS vars blank, or set `DB_SSL_MODE=prefer` to negotiate TLS if supported). | Simplest configuration for trusted/internal networks.                          |
| **Server-authenticated TLS**  | `DB_CONNECTION_STRING` as above; set `DB_SSL_MODE=verify-ca` or `verify-full`; point `DB_SSL_ROOT_CERT` at the mounted CA (e.g., `/certs/root.crt`).         | When the DB requires TLS and you only need to validate the server certificate. |
| **Mutual TLS (client certs)** | Same as above **plus** set `DB_SSL_CERT` and `DB_SSL_KEY` to mounted paths (e.g., `/certs/client.crt`, `/certs/client.key`).                                 | Managed services or hardened deployments that require client certificates.     |

> **Mounting certificates:** Place your CA/client certificates under `./certs/` (host).
> The compose file maps `./certs:/certs:ro`, so reference them in `.env` as `/certs/<file>`.

***

## 8) Applying Migrations

* The backend runs migrations automatically on first startup.
* Ensure your DB user has privileges to **CREATE**, **ALTER**, and **INSERT**.
* Watch `docker compose logs --tail=200 syn-cloud` for migration output on first boot.

***

## 9) Configure HTTPS Termination

Choose one of the following:

1. **Terminate TLS inside the app**
   * Set `CONNECTION_SECURITY=tls` and `PORT=443` in `.env`.
   * Put `fullchain.pem` and `privkey.pem` under `./certs/`.
   * Set in `.env`:
     `TLS_CERT=/certs/fullchain.pem`
     `TLS_KEY=/certs/privkey.pem`
   * (Optional) `FORWARD_PORT_80_TO_443=true` to redirect HTTP→HTTPS within the container.

2. **Terminate TLS at a reverse proxy (nginx, Caddy, Traefik, etc.)**
   * Leave `CONNECTION_SECURITY=none`, keep `PORT=80`.
   * Point your proxy at the container’s HTTP listener.
   * Manage certificates in the proxy; the app container does **not** need the private key.

3. **Hybrid / Load balancer offload**
   * Present certificates at the LB and forward HTTP to the container (`CONNECTION_SECURITY=none`).
   * Ensure LB forwards `X-Forwarded-Proto` so the app generates correct HTTPS links.

> When certificates change, restart to apply: `docker compose up -d` (or `--force-recreate`).

***

## 10) Edit configuration later

* Edit `.env` and re-apply:

```bash
docker compose up -d
# If changes don’t apply, force recreation:
docker compose up -d --force-recreate
```

* **Pin or roll back** to a specific image tag by setting `TAG` in `.env`, then:

```bash
docker compose pull
docker compose up -d
```

***

## 11) Troubleshooting

* **401 Unauthorized during pull** → Re-run the login:
  `docker login synaccess.azurecr.io -u <TOKEN_USER> -p '<TOKEN_PASSWORD>'`

* **DNS/network error**
  ```bash
  nslookup synaccess.azurecr.io
  curl -I https://synaccess.azurecr.io/v2/ || true
  ```

* **Database connection errors on first boot**
  Verify `DB_CONNECTION_STRING` and any TLS variables. Confirm mounted cert paths (`/certs/...`) exist.

***

## 12 What persists between restarts

* Any mapped paths in `volumes:` (e.g., `./certs`, `./logs`) persist on the host.
* If you add a DB service in your compose later, its data can live in `./pgdata`.

> Do **not** commit `.env` to version control. Keep credentials safe.

***

* <br />