Skip to content

Instantly share code, notes, and snippets.

@diegofcornejo
Created July 25, 2025 16:12
Show Gist options
  • Save diegofcornejo/e154ed04069602585ed6f8dd62a659f6 to your computer and use it in GitHub Desktop.
Save diegofcornejo/e154ed04069602585ed6f8dd62a659f6 to your computer and use it in GitHub Desktop.
The Things Stack Docker Installation Guide

The Things Stack Docker Installation Guide

This guide provides step-by-step instructions to set up The Things Stack using Docker, starting from configuration files provided separately.

Table of Contents

Useful Links

Prerequisites

Make sure you have the following software installed on your system:

  • Docker
  • Docker Compose
  • Git

Step 1: Get Required Files

First, download the necessary configuration files. These should be provided to you (e.g., from a Gist or a separate repository).

  • env.example
  • docker-compose.yml
  • ttn-lw-stack-docker.yml

Create a temporary working directory and place these three files inside it.

Step 2: Clone The Things Stack Repository

Now, clone the official repository from The Things Network. This command will create a lorawan-stack directory.

git clone https://github.com/TheThingsNetwork/lorawan-stack

Step 3: Set Up Configuration Files

Move the files you downloaded into the correct locations within the lorawan-stack directory.

Move docker-compose.yml and env.example

From your temporary directory (where you downloaded the files), run:

mv docker-compose.yml env.example lorawan-stack/

Move the stack configuration file

mv ttn-lw-stack-docker.yml lorawan-stack/config/stack/

Navigate into the repository directory

All subsequent commands should be run from inside the lorawan-stack directory.

cd lorawan-stack

Step 4: Configure Your Environment (.env file)

Use the env.example file as a template to create your local environment file.

cp env.example .env

Now, edit the .env file with your specific configuration. Pay close attention to the following:

  • NETWORK_HOST: Your public domain or localhost.
  • NETWORK_BASE_URL: The full URL, e.g., https://your.domain.com or https://localhost.
  • SENDER_ADDRESS, SMTP_ADDRESS, etc.: Your email sending credentials.
  • ACME_EMAIL, ACME_HOST: Your email and domain for Let's Encrypt certificates.
  • Security Secrets: These are critical. You must set secure, unique values.

You can use openssl to generate secrets from your terminal:

# For COOKIE_BLOCK_KEY (32 bytes)
openssl rand -hex 32

# For COOKIE_HASH_KEY (64 bytes)
openssl rand -hex 64

Ensure CONSOLE_CLIENT_SECRET is also set to a strong, unique secret.

Step 5: Prepare the Database and Services

These commands will initialize the database, create the administrator user, and set up OAuth clients. Docker Compose will automatically load the variables from your .env file.

Migrate the Database

docker compose run --rm stack is-db migrate

Create the Admin User

This creates the initial administrator user for your stack. Replace [email protected] with the admin's actual email.

docker compose run --rm stack is-db create-admin-user --id admin --email [email protected]

You will be prompted to enter and confirm a password for the admin user.

Create the OAuth Client for the CLI

docker compose run --rm stack is-db create-oauth-client \
  --id cli \
  --name "Command Line Interface" \
  --owner admin \
  --no-secret \
  --redirect-uri "local-callback" \
  --redirect-uri "code"

Create the OAuth Client for the Console

This command uses variables directly from your .env file to configure the Console.

docker compose run --rm stack is-db create-oauth-client \
  --id "console" \
  --name "Console" \
  --owner "admin" \
  --secret "${CONSOLE_CLIENT_SECRET}" \
  --redirect-uri "${NETWORK_BASE_URL}/console/oauth/callback" \
  --redirect-uri "/console/oauth/callback" \
  --logout-redirect-uri "${NETWORK_BASE_URL}/console" \
  --logout-redirect-uri "/console"

Step 6: Start The Things Stack

Once all preparation steps are complete, start the services in detached mode.

docker compose up -d

You can check the logs to ensure everything is running correctly:

docker compose logs -f

Step 7: Access the Console

You can now access the web console by navigating to the URL you configured as NETWORK_BASE_URL in your .env file. For example:

https://localhost/console

Or if you used a custom domain:

https://your.domain.com/console

Log in with your admin user ID (admin) and the password you created in Step 5.

version: '3.7'
services:
postgres:
# In production, replace 'latest' with tag from https://hub.docker.com/_/postgres?tab=tags
image: postgres:14
restart: unless-stopped
environment:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_DB=${POSTGRES_DB}
volumes:
- ${DEV_DATA_DIR:-.env/data}/postgres:/var/lib/postgresql/data
ports:
- "127.0.0.1:5432:5432"
redis:
# In production, replace 'latest' with tag from https://hub.docker.com/_/redis?tab=tags
image: redis:7.4.5
command: redis-server --appendonly yes
restart: unless-stopped
volumes:
- ${DEV_DATA_DIR:-.env/data}/redis:/data
ports:
- "127.0.0.1:6379:6379"
stack:
# In production, replace 'latest' with tag from https://hub.docker.com/r/thethingsnetwork/lorawan-stack/tags
image: thethingsnetwork/lorawan-stack:3.34
entrypoint: ttn-lw-stack -c /config/ttn-lw-stack-docker.yml
command: start
restart: unless-stopped
depends_on:
- redis
- postgres
volumes:
- ./blob:/srv/ttn-lorawan/public/blob
- ./config/stack:/config:ro
# If using Let's Encrypt:
- ./acme:/var/lib/acme
environment:
TTN_LW_BLOB_LOCAL_DIRECTORY: /srv/ttn-lorawan/public/blob
TTN_LW_REDIS_ADDRESS: redis:6379
TTN_LW_IS_DATABASE_URI: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}?sslmode=disable
ports:
# If deploying on a public server:
- "80:1885"
- "443:8885"
- "1881:1881"
- "8881:8881"
- "1882:1882"
- "8882:8882"
- "1883:1883"
- "8883:8883"
- "1884:1884"
- "8884:8884"
- "1885:1885"
- "8885:8885"
- "1886:1886"
- "8886:8886"
- "1887:1887"
- "8887:8887"
- "8889:8889"
- "1700:1700/udp"
# If using custom certificates:
# secrets:
# - ca.pem
# - cert.pem
# - key.pem
# If using custom certificates:
# secrets:
# ca.pem:
# file: ./ca.pem
# cert.pem:
# file: ./cert.pem
# key.pem:
# file: ./key.pem
# PostgreSQL Credentials
POSTGRES_USER=root
POSTGRES_PASSWORD=root
POSTGRES_DB=ttn_lorawan_dev # Do not change
# Data Directory (optional, defaults to ./.env/data in docker-compose.yml)
# DEV_DATA_DIR=./.env/data
# --- The Things Stack Configuration ---
# Network Configuration
# This is the host that will be used for the network endpoints. For local development, 'localhost' is fine.
# For production, this should be your public domain (e.g., lorawan.example.com).
NETWORK_HOST=localhost
# This is the base URL for the web console and APIs.
# For production, this should be https://<your_domain>
NETWORK_BASE_URL=https://localhost
# General network name
NETWORK_NAME="My LoRaWAN Network"
# Email Configuration (using SMTP)
# This is the "From" address for emails sent by the stack.
SENDER_ADDRESS=noreply@localhost
SMTP_ADDRESS=smtp.example.com:587
SMTP_USERNAME=user
SMTP_PASSWORD=password
# Security Keys (IMPORTANT: Change these for production!)
# These keys are used for securing cookies. Use the following commands to generate them:
# openssl rand -hex 32
COOKIE_BLOCK_KEY=CHANGEMECHANGEMECHANGEMECHANGEMECHANGEMECHANGEMECHANGEMECHANGEME
# openssl rand -hex 64
COOKIE_HASH_KEY=CHANGEMECHANGEMECHANGEMECHANGEMECHANGEMECHANGEMECHANGEMECHANGEMECHANGEMECHANGEMECHANGEMECHANGEMECHANGEMECHANGEMECHANGEMECHANGEME
# Metrics and PProf Passwords (IMPORTANT: Change these for production!)
METRICS_PASSWORD=CHANGEME
PPROF_PASSWORD=CHANGEME
# Let's Encrypt (ACME) Configuration
# Your email for Let's Encrypt notifications
[email protected]
# The public domain for which to get a certificate. Must match NETWORK_HOST in production.
ACME_HOST=localhost
# Console OAuth Client Secret (IMPORTANT: Change this for production!)
# This is a secret for the console to communicate with the Identity Server.
CONSOLE_CLIENT_SECRET=CHANGEME
# Identity Server configuration
# Email configuration for "lorawan-ns-01.diegocornejo.com"
is:
email:
sender-name: ${NETWORK_NAME}
sender-address: ${SENDER_ADDRESS}
network:
name: ${NETWORK_NAME}
console-url: ${NETWORK_BASE_URL}/console
identity-server-url: ${NETWORK_BASE_URL}/oauth
# If sending email with Sendgrid
# provider: sendgrid
# sendgrid:
# api-key: '...' # enter Sendgrid API key
# If sending email with SMTP
provider: smtp
smtp:
address: ${SMTP_ADDRESS}
username: ${SMTP_USERNAME}
password: ${SMTP_PASSWORD}
user-registration:
enabled: false
# Web UI configuration for "lorawan-ns-01.diegocornejo.com":
oauth:
ui:
canonical-url: ${NETWORK_BASE_URL}/oauth
is:
base-url: ${NETWORK_BASE_URL}/api/v3
# HTTP server configuration
http:
cookie:
block-key: ${COOKIE_BLOCK_KEY} # generate 32 bytes (openssl rand -hex 32)
hash-key: ${COOKIE_HASH_KEY} # generate 64 bytes (openssl rand -hex 64)
metrics:
password: ${METRICS_PASSWORD} # choose a password
pprof:
password: ${PPROF_PASSWORD} # choose a password
# If using custom certificates:
# tls:
# source: file
# root-ca: /run/secrets/ca.pem
# certificate: /run/secrets/cert.pem
# key: /run/secrets/key.pem
# Let's encrypt for "lorawan-ns-01.diegocornejo.com"
tls:
source: acme
acme:
dir: /var/lib/acme
email: ${ACME_EMAIL}
hosts: ["${ACME_HOST}"]
default-host: ${ACME_HOST}
# If Gateway Server enabled, defaults for "lorawan-ns-01.diegocornejo.com":
gs:
mqtt:
public-address: ${NETWORK_HOST}:1882
public-tls-address: ${NETWORK_HOST}:8882
mqtt-v2:
public-address: ${NETWORK_HOST}:1881
public-tls-address: ${NETWORK_HOST}:8881
# If Gateway Configuration Server enabled, defaults for "lorawan-ns-01.diegocornejo.com":
gcs:
basic-station:
default:
lns-uri: "wss://${NETWORK_HOST}:8887"
# Web UI configuration for "lorawan-ns-01.diegocornejo.com":
console:
ui:
canonical-url: ${NETWORK_BASE_URL}/console
account-url: ${NETWORK_BASE_URL}/oauth
is:
base-url: ${NETWORK_BASE_URL}/api/v3
gs:
base-url: ${NETWORK_BASE_URL}/api/v3
ns:
base-url: ${NETWORK_BASE_URL}/api/v3
as:
base-url: ${NETWORK_BASE_URL}/api/v3
js:
base-url: ${NETWORK_BASE_URL}/api/v3
gcs:
base-url: ${NETWORK_BASE_URL}/api/v3
qrg:
base-url: ${NETWORK_BASE_URL}/api/v3
edtc:
base-url: ${NETWORK_BASE_URL}/api/v3
dcs:
base-url: ${NETWORK_BASE_URL}/api/v3
oauth:
authorize-url: ${NETWORK_BASE_URL}/oauth/authorize
token-url: ${NETWORK_BASE_URL}/oauth/token
logout-url: ${NETWORK_BASE_URL}/oauth/logout
client-id: "console"
client-secret: ${CONSOLE_CLIENT_SECRET} # choose or generate a secret
# If Application Server enabled, defaults for "lorawan-ns-01.diegocornejo.com":
as:
mqtt:
public-address: ${NETWORK_HOST}:1883
public-tls-address: ${NETWORK_HOST}:8883
webhooks:
downlink:
public-address: ${NETWORK_HOST}:1885/api/v3
# Managed gateway configuration, defaults for "lorawan-ns-01.diegocornejo.com".
# This configures a connection with The Things Gateway Controller, a service operated by The Things Industries.
# This allows connecting, for example, The Things Indoor Gateway Pro.
# ttgc:
# enabled: true
# domain: lorawan-ns-01.diegocornejo.com
# # Let's Encrypt:
# tls:
# source: acme
# # If using custom certificates:
# # tls:
# # source: file
# # certificate: /run/secrets/cert.pem
# # key: /run/secrets/key.pem
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment