Docker
Deployment Guide
This guide covers deploying Team Server using Docker and Docker Compose. Choose the method that best fits your infrastructure and requirements.
Prerequisites
Before starting, ensure you have:
- Docker Engine 24.0+ installed and running
- Docker Compose v2.0+ (for Docker Compose deployment)
- PostgreSQL database (local or remote)
- Team Server configuration file (
production.yaml) - At least 2GB of available RAM
- 10GB of available disk space
Method 1: Docker Compose (Recommended)
Docker Compose provides the easiest way to deploy Team Server with all its dependencies.
Step 1: Create Project Directory
mkdir team-server
cd team-serverStep 2: Create Configuration Directory
Create the configuration directory and file:
mkdir configCreate config/production.yaml with your Team Server configuration. For more information about the configuration file, visit the comprehensive documentation here.
Note: The configuration file uses environment variable templating (e.g., {{ get_env(name="VAR_NAME") }}). Set these environment variables before starting the container.
# Minimal example configuration
logger:
enable: true
level: info
format: compact
scheduler:
output: stdout
jobs:
calculate_statistics:
run: "calculate_statistics"
schedule: "0 0 5 * * * *"
update_status:
run: "update_status"
schedule: "0 */5 * * * * *"
server:
binding: 0.0.0.0
port: 5150
host: "{{ get_env(name='SERVER_URL') }}"
middlewares:
compression:
enable: true
cors:
enable: true
allow_origins:
- "{{ get_env(name='SERVER_URL') }}"
allow_headers:
- "*"
allow_methods:
- "*"
fallback:
enable: false
limit_payload:
body_limit: 5mb
logger:
enable: true
secure_headers:
preset: github
static:
enable: true
must_exist: false
folder:
uri: "/"
path: "public"
fallback: "public/index.html"
database:
uri: "{{ get_env(name='DATABASE_URL') }}"
auto_migrate: true
connect_timeout: 1500
enable_logging: false
idle_timeout: 500
max_connections: 2
min_connections: 2
workers:
mode: BackgroundAsync
settings:
jwt:
sensor:
secret: "{{ get_env(name='JWT_SENSOR_SECRET') }}"
expiration: 31557600 # 1 year in seconds
user:
secret: "{{ get_env(name='JWT_USER_SECRET') }}"
expiration: 604800 # 7 days in seconds
tenant:
name: "{{ get_env(name='TENANT_NAME') }}"
base_url: "{{ get_env(name='SERVER_URL') }}"
# TODO: configure your preferred authentication providerFinally, update the production.yaml configuration file to reflect your preferred authentication provider.
Step 3: Create Environment File
Create a .env file with your secrets (never commit this file to version control):
Generate .env with secrets:
PGDB=team_server
PGPASS=$(openssl rand -hex 16)
PGUSER=team_server
cat > .env <<EOF
POSTGRES_DB=$PGDB
POSTGRES_PASSWORD=$PGPASS
POSTGRES_USER=$PGUSER
DATABASE_URL=postgresql://$PGUSER:$PGPASS@postgres:5432/$PGDB
JWT_SENSOR_SECRET=$(openssl rand -hex 32)
JWT_USER_SECRET=$(openssl rand -hex 32)
EOFThen update the .env file to include SERVER_URL and TENANT_NAME:
# .env file - keep this secure and never commit to git
POSTGRES_DB=team_server
POSTGRES_PASSWORD=[redacted]
POSTGRES_USER=team_server
DATABASE_URL=postgresql://team_server:[redacted]@postgres:5432/team_server
JWT_SENSOR_SECRET=[redacted]
JWT_USER_SECRET=[redacted]
SERVER_URL=http://localhost:5150
TENANT_NAME=<your_organization_name>Step 4: Create Docker Compose Configuration
Create a docker-compose.yaml file:
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
endura-team-server:
image: ghcr.io/endurasecurity/container/endura-team-server:testing
ports:
- "5150:5150"
environment:
- DATABASE_URL=${DATABASE_URL}
- JWT_SENSOR_SECRET=${JWT_SENSOR_SECRET}
- JWT_USER_SECRET=${JWT_USER_SECRET}
- SERVER_URL=${SERVER_URL}
- TENANT_NAME=${TENANT_NAME}
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "${SERVER_URL}/_readiness"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
volumes:
- ./config:/app/config
volumes:
postgres_data:Important Security Notes:
- Never commit the
.envfile to version control (add it to.gitignore) - Generate secure, random values for all secrets (minimum 32 characters)
- For production deployments, use Docker secrets or external secret management
- Ensure the database password in
.envmatches betweenPOSTGRES_PASSWORDandDATABASE_URL
Step 5: Deploy Team Server
# Start all services
docker compose up -d
# View logs to monitor startup
docker compose logs -f endura-team-serverStep 6: Verify Deployment
Check that all services are running:
# Check service status
docker compose ps
# Verify Team Server health
curl http://localhost:5150/_readiness
# Check application logs
docker compose logs endura-team-serverYou should see:
- All services in “Up” status
- Health checks passing with a 200 status and body of {“ok”:true}
- No error messages in logs
Method 2: Direct Docker Deployment
For deployments where you manage PostgreSQL separately.
Prerequisites
Before proceeding, ensure you have a PostgreSQL instance running with a dedicated database for Team Server. The steps below assume the following setup:
- PostgreSQL is running in a container named
postgreson port 5432 - A user named
team_serverexists with full read/write access to a database also namedteam_server
Adjust the commands as needed to match your environment.
Step 1: Create Project Directory
mkdir team-server
cd team-serverStep 2: Create Configuration Directory
Create the configuration directory and file:
mkdir configCreate config/production.yaml with your Team Server configuration. For more information about the configuration file, visit the comprehensive documentation here.
Note: The configuration file uses environment variable templating (e.g., {{ get_env(name="VAR_NAME") }}). Set these environment variables before starting the container.
# Minimal example configuration
logger:
enable: true
level: info
format: compact
scheduler:
output: stdout
jobs:
calculate_statistics:
run: "calculate_statistics"
schedule: "0 0 5 * * * *"
update_status:
run: "update_status"
schedule: "0 */5 * * * * *"
server:
binding: 0.0.0.0
port: 5150
host: "{{ get_env(name='SERVER_URL') }}"
middlewares:
compression:
enable: true
cors:
enable: true
allow_origins:
- "{{ get_env(name='SERVER_URL') }}"
allow_headers:
- "*"
allow_methods:
- "*"
fallback:
enable: false
limit_payload:
body_limit: 5mb
logger:
enable: true
secure_headers:
preset: github
static:
enable: true
must_exist: false
folder:
uri: "/"
path: "public"
fallback: "public/index.html"
database:
uri: "{{ get_env(name='DATABASE_URL') }}"
auto_migrate: true
connect_timeout: 1500
enable_logging: false
idle_timeout: 500
max_connections: 2
min_connections: 2
workers:
mode: BackgroundAsync
settings:
jwt:
sensor:
secret: "{{ get_env(name='JWT_SENSOR_SECRET') }}"
expiration: 31557600 # 1 year in seconds
user:
secret: "{{ get_env(name='JWT_USER_SECRET') }}"
expiration: 604800 # 7 days in seconds
tenant:
name: "{{ get_env(name='TENANT_NAME') }}"
base_url: "{{ get_env(name='SERVER_URL') }}"
# TODO: configure your preferred authentication providerFinally, update the production.yaml configuration file to reflect your preferred authentication provider.
Step 3: Create Environment File
Create a .env file with your secrets (never commit this file to version control):
Generate .env with secrets:
PGDB=team_server
PGPASS=$(openssl rand -hex 16)
PGUSER=team_server
cat > .env <<EOF
POSTGRES_DB=$PGDB
POSTGRES_PASSWORD=$PGPASS
POSTGRES_USER=$PGUSER
DATABASE_URL=postgresql://$PGUSER:$PGPASS@postgres:5432/$PGDB
JWT_SENSOR_SECRET=$(openssl rand -hex 32)
JWT_USER_SECRET=$(openssl rand -hex 32)
EOFThen update the .env file to include SERVER_URL and TENANT_NAME:
# .env file - keep this secure and never commit to git
POSTGRES_DB=team_server
POSTGRES_PASSWORD=[redacted]
POSTGRES_USER=team_server
DATABASE_URL=postgresql://team_server:[redacted]@postgres:5432/team_server
JWT_SENSOR_SECRET=[redacted]
JWT_USER_SECRET=[redacted]
SERVER_URL=http://localhost:5150
TENANT_NAME=<your_organization_name>Step 3: Create Docker Network (Optional)
Run the following command to create a network shared between PostgreSQL and Team Server:
docker network create endura-team-serverNote: This is optional and only required if PostgreSQL and Team Server will communicate between containers on a shared host.
Step 4: Run PostgreSQL Container (Optional)
Create a directory to store your PostgreSQL data and serve as a Docker volume:
mkdir dataRun your preferred PostgreSQL container similar to the following command, ensuring you specify --env-file for secrets access and -v for mounting the data volume.
docker run -d \
--name postgres \
--network endura-team-server \
--env-file .env \
-p 5432:5432 \
-v data:/var/lib/postgresql/data \
--restart unless-stopped \
postgres:16-alpineNote: This is optional and only required if PostgreSQL is to be run as a stand-alone container.
Step 5: Run Team Server Container
Run the Team Server container similar to the following command, ensuring you specify --env-file for secrets access and -v for mounting the configuration volume.
docker run -d \
--name endura-team-server \
--network endura-team-server \
--user $(id -u):$(id -g) \
--env-file .env \
-p 5150:5150 \
-v ./config:/app/config \
--restart unless-stopped \
ghcr.io/endurasecurity/container/endura-team-server:testingStep 6: Verify Deployment
Check that all services are running:
# Check service status
docker ps
# Verify Team Server health
curl http://localhost:5150/_readiness
# Check application logs
docker logs endura-team-serverYou should see:
- All services in “Up” status
- Health checks passing with a 200 status and body of {“ok”:true}
- No error messages in logs
Systemd Service Configuration
To ensure Team Server starts automatically on system boot and can be managed using standard systemd commands, you can create a systemd service file.
Docker Compose Method
Create a systemd service file at /etc/systemd/system/endura-team-server.service:
[Unit]
Description=Endura Team Server (Docker Compose)
Requires=docker.service
After=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/path/to/team-server
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0
[Install]
WantedBy=multi-user.targetReplace /path/to/team-server with the absolute path to your project directory containing the docker-compose.yaml file.
Enable and start the service:
# Reload systemd to recognize the new service
sudo systemctl daemon-reload
# Enable the service to start on boot
sudo systemctl enable endura-team-server.service
# Start the service
sudo systemctl start endura-team-server.service
# Check service status
sudo systemctl status endura-team-server.serviceDirect Docker Method
Create a systemd service file at /etc/systemd/system/endura-team-server.service:
[Unit]
Description=Endura Team Server (Docker)
Requires=docker.service
After=docker.service
[Service]
Type=simple
Restart=always
RestartSec=5
WorkingDirectory=/path/to/team-server
ExecStartPre=-/usr/bin/docker rm -f endura-team-server
ExecStart=/usr/bin/docker run --rm \
--name endura-team-server \
--network endura-team-server \
--user %U:%G \
--env-file /path/to/team-server/.env \
-p 5150:5150 \
-v /path/to/team-server/config:/app/config \
ghcr.io/endurasecurity/container/endura-team-server:testing
ExecStop=/usr/bin/docker stop endura-team-server
[Install]
WantedBy=multi-user.targetReplace /path/to/team-server with the absolute path to your project directory. Add any additional volume mounts (such as TLS certificates) as needed.
Enable and start the service:
# Reload systemd to recognize the new service
sudo systemctl daemon-reload
# Enable the service to start on boot
sudo systemctl enable endura-team-server.service
# Start the service
sudo systemctl start endura-team-server.service
# Check service status
sudo systemctl status endura-team-server.serviceManaging the Service
Common systemd commands for managing the service:
# View service logs
sudo journalctl -u endura-team-server.service -f
# Restart the service
sudo systemctl restart endura-team-server.service
# Stop the service
sudo systemctl stop endura-team-server.service
# Disable auto-start on boot
sudo systemctl disable endura-team-server.serviceSet Up Your First Administrator
When users first access Team Server, they are automatically assigned the Viewer role with read-only access. To manage Team Server, you need to promote at least one user to the Administrator role.
User Must Log In First
The user must access Team Server and complete authentication before you can change their role. This creates their user record in the database. If you run these commands before the user has logged in, they will fail because the email address will not be found.
Step 1: Log In to Team Server
Open your browser and navigate to your Team Server URL. Complete the authentication process using your configured OIDC provider. This creates your user record in the database.
Step 2: Get the User ID
Run the following command, replacing the email address with your own:
docker compose exec endura-team-server endura task user_get_id email:your-email@example.comdocker exec endura-team-server endura task user_get_id email:your-email@example.comThis command outputs a numeric user ID.
Step 3: Assign the Administrator Role
Run the following command, replacing <user_id> with the numeric ID from the previous step:
docker compose exec endura-team-server endura task user_set_role id:<user_id> role:administratordocker exec endura-team-server endura task user_set_role id:<user_id> role:administratorStep 4: Verify the Role Change
Refresh your browser or log out and log back in to Team Server. You should now see an Administration menu item in the main navigation, confirming your Administrator role is active.
TLS Configuration
Team Server supports TLS termination directly within the application. This method provides end-to-end encryption without requiring external TLS proxies.
When to Use TLS
Use TLS configuration when:
- You need end-to-end encryption for production deployments
- You want to avoid external load balancers or reverse proxies
- You have your own SSL certificates to use
- You’re deploying in environments where external TLS termination isn’t available
For development: You can use self-signed certificates for testing, but browsers will show security warnings.
Step 1: Prepare TLS Certificates
Create or obtain your TLS certificate files:
# Create a certificates directory
mkdir certs
# Example: Create self-signed certificates for testing
openssl req -x509 \
-newkey rsa:4096 \
-keyout certs/server-key.pem \
-out certs/server.pem \
-days 365 \
-nodes \
-subj "/C=US/ST=State/L=City/O=MyOrg/CN=team-server.your-domain.com"Step 2: Update Configuration File
Update settings to contain a tls section in your config/production.yaml file. Secrets are mounted at /run/secrets/ inside the container:
settings:
tls:
certificate: "/run/secrets/server_cert"
private_key: "/run/secrets/server_key"Step 3: Update Environment File
Update your .env file to use HTTPS and add user ID variables:
# Add HTTPS URL
echo "SERVER_URL=https://localhost:5150" >> .env
# Add user/group IDs (required for secret file permissions)
echo "UID=$(id -u)" >> .env
echo "GID=$(id -g)" >> .envStep 4: Configure TLS with Secrets
Docker Compose Method
Update your docker-compose.yaml to include secrets:
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
endura-team-server:
image: ghcr.io/endurasecurity/container/endura-team-server:testing
user: "${UID}:${GID}"
ports:
- "5150:5150"
environment:
- DATABASE_URL=${DATABASE_URL}
- JWT_SENSOR_SECRET=${JWT_SENSOR_SECRET}
- JWT_USER_SECRET=${JWT_USER_SECRET}
- SERVER_URL=${SERVER_URL}
- TENANT_NAME=${TENANT_NAME}
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "-k", "${SERVER_URL}/_readiness"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
volumes:
- ./config:/app/config
secrets:
- server_cert
- server_key
secrets:
server_cert:
file: ./certs/server.pem
server_key:
file: ./certs/server-key.pem
volumes:
postgres_data:Then restart the services:
docker compose down
docker compose up -dDirect Docker Method
For standalone Docker without Swarm, mount the certificate files directly to the secrets path:
# Stop and remove existing container
docker rm --force endura-team-server
# Run with certificate files mounted as secrets
docker run -d \
--name endura-team-server \
--network endura-team-server \
--user $(id -u):$(id -g) \
--env-file .env \
-p 5150:5150 \
-v ./config:/app/config \
-v ./certs/server.pem:/run/secrets/server_cert:ro \
-v ./certs/server-key.pem:/run/secrets/server_key:ro \
--restart unless-stopped \
ghcr.io/endurasecurity/container/endura-team-server:testingThe --user flag runs the container as your host user, allowing access to the certificate files. The :ro flag mounts the certificates as read-only for additional security.
Step 5: Verify TLS Configuration
# Verify TLS is working (note the https:// and -k flag for self-signed certs)
curl -k https://localhost:5150/_readiness
# Check application logs for TLS startup messages
docker compose logs endura-team-server | grep -i tls
# For direct container
docker logs endura-team-server | grep -i tlsSecurity Notes for Production:
- Use certificates from a trusted Certificate Authority
- Store certificate files securely and limit file permissions
- Docker Compose secrets are mounted with secure permissions automatically
- Regularly rotate certificates before expiration
- Use strong cipher suites and TLS 1.2+ only
Updating Team Server
Docker Compose Method
# Stop current deployment
docker compose down
# Pull specific version by updating the image tag first
docker compose pull ghcr.io/endurasecurity/container/endura-team-server:testing
# Start with new image
docker compose up -d
# Verify update
docker compose logs endura-team-serverDirect Docker Method
# Stop current container
docker rm --force endura-team-server
# Pull specific version
docker pull ghcr.io/endurasecurity/container/endura-team-server:testing
# Run with new image (use same command as initial deployment)
docker run -d \
--name endura-team-server \
[... same parameters as before ...]
ghcr.io/endurasecurity/container/endura-team-server:testingBackup and Restore
Backup
# Backup PostgreSQL database via Docker Compose
docker compose exec postgres pg_dump -U team_server team_server > team_server_backup.sql
# Backup PostgreSQL database via Direct Docker
docker exec postgres pg_dump -U team_server team_server > team_server_backup.sql
# Backup configuration files
tar czf team_server_config_backup.tar.gz config/Restore
# Restore PostgreSQL database via Docker Compose
docker compose exec -T postgres psql -U team_server team_server < team_server_backup.sql
# Restore PostgreSQL database via Direct Docker
docker exec postgres psql -U team_server team_server < team_server_backup.sql
# Restore configuration files
tar xzf team_server_config_backup.tar.gzUninstalling Team Server
Docker Compose Method
# Stop and remove all services
docker compose down
# Remove volumes (WARNING: This deletes all data)
docker compose down -v
# Remove images
docker rmi ghcr.io/endurasecurity/container/endura-team-server:testing postgres:16
# Clean up unused resources
docker system prune -fDirect Docker Method
# Stop and remove endura-team-server
docker rm --force endura-team-server
# (Optional) Stop and remove postgres
docker rm --force postgres
# Remove network
docker network rm endura-team-server
# Remove image
docker rmi ghcr.io/endurasecurity/container/endura-team-server:testing
# Clean up unused resources
docker system prune -fTroubleshooting
Common Issues
Container fails to start:
# Check logs for Docker Compose
docker compose logs endura-team-server
# Check logs for Direct Docker
docker logs endura-team-server
# Verify configuration
docker compose config
# Check environment variables (be careful not to expose secrets in logs)
docker compose exec endura-team-server env | grep -E '(SERVER_URL|TENANT_NAME)'Database connection issues:
When using Docker Compose:
# Check PostgreSQL logs
docker compose logs postgresWhen using Direct Docker:
# Check PostgreSQL logs
docker logs postgresPermission issues:
# Check configuration file permissions
ls -la config/
# Fix ownership if needed
sudo chown -R $(id -u):$(id -g) config/
chmod -R ug+rx config/Memory issues:
# Check memory usage
docker stats
# Check Docker resource limits
docker inspect endura-team-server | grep -i memoryGetting Help
If you encounter issues:
- Check the logs:
docker compose logs endura-team-server - Verify all environment variables and configuration files are set correctly
- Ensure PostgreSQL is healthy and configuration file is mounted correctly
- Check network connectivity between containers
- Verify container image accessibility
For additional support, contact us at support@endurasecurity.com