Podman
Deployment Guide
This guide covers deploying Team Server using Podman and Podman Compose. Choose the method that best fits your infrastructure and requirements.
Prerequisites
Before starting, ensure you have:
- Podman 4.9+ installed and running
- Podman Compose v1.0+ (for Podman Compose deployment)
- For rootless mode: proper user namespace configuration and loginctl linger enabled
- For SELinux systems: container_manage_cgroup boolean enabled
- PostgreSQL database (local or remote)
- Team Server configuration file (
production.yaml) - At least 2GB of available RAM
- 10GB of available disk space
Method 1: Podman Compose (Recommended)
Podman 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 Podman Compose Configuration
Create a podman-compose.yaml file:
services:
postgres:
image: docker.io/postgres:16-alpine
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data:Z
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
userns_mode: "keep-id"
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:z
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 Podman secrets, Kubernetes secrets, or external secret management
- Ensure the database password in
.envmatches betweenPOSTGRES_PASSWORDandDATABASE_URL - Volume mounts use SELinux labels:
:zfor shared content,:Zfor private content
Step 5: Deploy Team Server
# Start all services
podman compose up -d
# View logs to monitor startup
podman compose logs -f endura-team-serverStep 6: Verify Deployment
Check that all services are running:
# Check service status
podman compose ps
# Verify Team Server health
curl http://localhost:5150/_readiness
# Check application logs
podman 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 Podman 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 Podman Network (Optional)
Run the following command to create a network shared between PostgreSQL and Team Server:
podman 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 Podman 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.
podman run -d \
--name postgres \
--network endura-team-server \
--env-file .env \
-p 5432:5432 \
-v data:/var/lib/postgresql/data \
--restart unless-stopped \
docker.io/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.
podman run -d \
--name endura-team-server \
--network endura-team-server \
--userns=keep-id \
--env-file .env \
-p 5150:5150 \
-v ./config:/app/config:z \
--restart unless-stopped \
ghcr.io/endurasecurity/container/endura-team-server:testingStep 6: Verify Deployment
Check that all services are running:
# Check service status
podman ps
# Verify Team Server health
curl http://localhost:5150/_readiness
# Check application logs
podman 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. Podman integrates well with systemd and can generate service files automatically.
Prerequisites for Rootless Podman
For rootless Podman containers to persist after logout, enable loginctl linger:
# Enable linger for your user
loginctl enable-linger $USER
# Verify linger is enabled
loginctl show-user $USER | grep LingerPodman Compose Method
Create a systemd user service file at ~/.config/systemd/user/endura-team-server.service:
mkdir -p ~/.config/systemd/user[Unit]
Description=Endura Team Server (Podman Compose)
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/path/to/team-server
ExecStart=/usr/bin/podman compose up -d
ExecStop=/usr/bin/podman compose down
TimeoutStartSec=0
[Install]
WantedBy=default.targetReplace /path/to/team-server with the absolute path to your project directory containing the podman-compose.yaml file.
Enable and start the service:
# Reload systemd to recognize the new service
systemctl --user daemon-reload
# Enable the service to start on boot
systemctl --user enable endura-team-server.service
# Start the service
systemctl --user start endura-team-server.service
# Check service status
systemctl --user status endura-team-server.serviceDirect Podman Method
Podman can generate systemd service files automatically from running containers:
# Ensure the container is running first
podman ps
# Generate a systemd user service file
mkdir -p ~/.config/systemd/user
podman generate systemd --name endura-team-server --files --new
mv container-endura-team-server.service ~/.config/systemd/user/endura-team-server.serviceThe --new flag creates a service that recreates the container on each start, which is recommended for updates.
Alternatively, create the service file manually at ~/.config/systemd/user/endura-team-server.service:
[Unit]
Description=Endura Team Server (Podman)
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
Restart=always
RestartSec=5
WorkingDirectory=/path/to/team-server
ExecStartPre=-/usr/bin/podman rm -f endura-team-server
ExecStart=/usr/bin/podman run --rm \
--name endura-team-server \
--network endura-team-server \
--userns=keep-id \
--env-file /path/to/team-server/.env \
-p 5150:5150 \
-v /path/to/team-server/config:/app/config:z \
ghcr.io/endurasecurity/container/endura-team-server:testing
ExecStop=/usr/bin/podman stop endura-team-server
[Install]
WantedBy=default.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
systemctl --user daemon-reload
# Enable the service to start on boot
systemctl --user enable endura-team-server.service
# Start the service
systemctl --user start endura-team-server.service
# Check service status
systemctl --user status endura-team-server.serviceManaging the Service
Common systemd commands for managing the service:
# View service logs
journalctl --user -u endura-team-server.service -f
# Restart the service
systemctl --user restart endura-team-server.service
# Stop the service
systemctl --user stop endura-team-server.service
# Disable auto-start on boot
systemctl --user 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:
podman compose exec endura-team-server endura task user_get_id email:your-email@example.compodman 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:
podman compose exec endura-team-server endura task user_set_role id:<user_id> role:administratorpodman 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"
# Ensure your user can read the certificate files
chown -R $(id -u):$(id -g) certs
chmod -R ug+r certsStep 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 SERVER_URL in your .env file to use HTTPS:
SERVER_URL=https://localhost:5150Step 4: Configure TLS with Secrets
Podman Compose Method
Update your podman-compose.yaml to include secrets:
services:
postgres:
image: docker.io/postgres:16-alpine
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data:Z
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
userns_mode: "keep-id"
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:z
- ./certs/server.pem:/run/secrets/server_cert:ro,z
- ./certs/server-key.pem:/run/secrets/server_key:ro,z
volumes:
postgres_data:Then restart the services:
podman compose down
podman compose up -dDirect Podman Method
For standalone Podman deployment, mount the certificate files directly to the secrets path:
# Stop and remove existing container
podman rm --force endura-team-server
# Run with certificate files mounted as secrets
podman run -d \
--name endura-team-server \
--network endura-team-server \
--userns=keep-id \
--env-file .env \
-p 5150:5150 \
-v ./config:/app/config:z \
-v ./certs/server.pem:/run/secrets/server_cert:ro,z \
-v ./certs/server-key.pem:/run/secrets/server_key:ro,z \
--restart unless-stopped \
ghcr.io/endurasecurity/container/endura-team-server:testingThe --userns=keep-id flag maps your host user directly into the container, allowing access to the certificate files with standard Unix permissions. 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
podman compose logs endura-team-server | grep -i tls
# For direct container
podman logs endura-team-server | grep -i tls
# Verify secrets are mounted correctly
podman exec endura-team-server ls -la /run/secrets/Security Notes for Production:
- Use certificates from a trusted Certificate Authority
- Store certificate files securely and limit file permissions
- Regularly rotate certificates before expiration
- Use strong cipher suites and TLS 1.2+ only
Updating Team Server
Podman Compose Method
# Stop current deployment
podman compose down
# Pull specific version by updating the image tag first
podman compose pull ghcr.io/endurasecurity/container/endura-team-server:testing
# Start with new image
podman compose up -d
# Verify update
podman compose logs endura-team-serverDirect Podman Method
# Stop current container
podman rm --force endura-team-server
# Pull specific version
podman pull ghcr.io/endurasecurity/container/endura-team-server:testing
# Run with new image (use same command as initial deployment)
podman 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 Podman Compose
podman compose exec postgres pg_dump -U team_server team_server > team_server_backup.sql
# Backup PostgreSQL database via Direct Podman
podman 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 Podman Compose
podman compose exec -T postgres psql -U team_server team_server < team_server_backup.sql
# Restore PostgreSQL database via Direct Podman
podman 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
Podman Compose Method
# Stop and remove all services
podman compose down
# Remove volumes (WARNING: This deletes all data)
podman compose down -v
# Remove images
podman rmi ghcr.io/endurasecurity/container/endura-team-server:testing postgres:16
# Clean up unused resources
podman system prune -fDirect Podman Method
# Stop and remove endura-team-server
podman rm --force endura-team-server
# (Optional) Stop and remove postgres
podman rm --force postgres
# Remove network
podman network rm endura-team-server
# Remove image
podman rmi ghcr.io/endurasecurity/container/endura-team-server:testing
# Clean up unused resources
podman system prune -fTroubleshooting
Common Issues
Container fails to start:
# Check logs for Podman Compose
podman compose logs endura-team-server
# Check logs for Direct Podman
podman logs endura-team-server
# Verify configuration
podman compose config
# Check environment variables (be careful not to expose secrets in logs)
podman compose exec endura-team-server env | grep -E '(SERVER_URL|TENANT_NAME)'Database connection issues:
When using Podman Compose:
# Check PostgreSQL logs
podman compose logs postgresWhen using Direct Podman:
# Check PostgreSQL logs
podman 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/Rootless Podman issues:
# Check if running rootless
podman info | grep -i rootless
# Verify user namespace mapping
podman unshare cat /proc/self/uid_map
# Check for SELinux issues (if applicable)
sudo sealert -a /var/log/audit/audit.log
# Ensure proper cgroup configuration
sudo setsebool -P container_manage_cgroup true
# Check if loginctl linger is enabled for persistent services
loginctl show-user $USER | grep LingerMemory issues:
# Check memory usage
podman stats
# Check systemd resource limits for rootless mode
systemctl --user show user.slice | grep Memory
# For rootless mode, check if cgroup v2 is enabled
cat /sys/fs/cgroup/cgroup.controllersGetting Help
If you encounter issues:
- Check the logs:
podman 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 rootless issues, check user namespace, SELinux configuration, and cgroup settings
- Ensure volume mounts use proper SELinux labels (:z for shared, :Z for private)
- Verify loginctl linger is enabled for persistent rootless containers
For additional support, contact us at support@endurasecurity.com