Podman
Deploy Team Server with Podman Compose and a local PostgreSQL container, or run it rootless against an existing PostgreSQL service.
Prerequisites
- Podman 4.4 or later
- A Compose provider for the Compose method
- PostgreSQL 18 or a compatible managed PostgreSQL service
- At least 2 GB of memory and 10 GB of disk for Team Server
- A Team Server license and an OIDC application for production authentication
- A public HTTPS URL reachable by users and Runtime Sensors
For rootless services that must survive logout and reboot, enable lingering:
sudo loginctl enable-linger "$USER"Prepare Configuration
mkdir -p team-server/config
cd team-serverSave the production configuration example as config/production.yaml. Before deployment:
- Add the discovery jobs for the CI/CD providers you use.
- Configure at least one OIDC provider.
- Add outbound HTTP policy for private identity providers, integration endpoints, or internal certificate authorities.
- Confirm that
server.host, CORS, andsettings.tenant.base_urluseSERVER_URL.
Generate a credential-encryption key, two distinct JWT secrets, and a database password:
openssl rand -base64 32
openssl rand -base64 48
openssl rand -base64 48
openssl rand -hex 24Create .env:
POSTGRES_DB=team_server
POSTGRES_USER=team_server
POSTGRES_PASSWORD=<generated_database_password>
DATABASE_URL=postgresql://team_server:<generated_database_password>@postgres:5432/team_server
ENDURA_CREDENTIAL_ENCRYPTION_KEY=<credential_encryption_key>
ENDURA_LICENSE_KEY=<license_key>
JWT_SENSOR_SECRET=<first_base64_secret>
JWT_USER_SECRET=<second_base64_secret>
SERVER_URL=https://team-server.example.com
TENANT_NAME=Example Organization
GOOGLE_OIDC_CLIENT_ID=<client_id>
GOOGLE_OIDC_CLIENT_SECRET=<client_secret>Use the variables referenced by your selected OIDC provider, then protect the file:
chmod 600 .env
printf '%s\n' '.env' >> .gitignoreStartup requires a 32-byte encryption key and two different JWT secrets of at least 32 bytes each.
Method 1: Podman Compose
Create podman-compose.yaml:
services:
postgres:
image: docker.io/postgres:18-alpine
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
endura-team-server:
image: ghcr.io/endurasecurity/container/endura-team-server:testing
env_file: .env
ports:
- "5150:5150"
volumes:
- ./config:/app/config:ro,Z
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://localhost:5150/_readiness"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
restart: unless-stopped
volumes:
postgres_data:Start and verify the services:
podman compose -f podman-compose.yaml up -d
podman compose -f podman-compose.yaml logs -f endura-team-server
podman compose -f podman-compose.yaml ps
curl -f http://localhost:5150/_readinessReadiness returns HTTP 200 with {"ok":true} after startup completes.
Method 2: Existing PostgreSQL
Update DATABASE_URL in .env with the external database host, then run Team Server rootless:
podman run -d \
--name endura-team-server \
--userns=keep-id \
--env-file .env \
-p 5150:5150 \
-v "$PWD/config:/app/config:ro,Z" \
--restart unless-stopped \
ghcr.io/endurasecurity/container/endura-team-server:testingVerify startup:
podman ps --filter name=endura-team-server
podman logs endura-team-server
curl -f http://localhost:5150/_readinessRun Direct Podman with Quadlet
Quadlet gives a rootless direct deployment a persistent user service. Use absolute paths for the environment file and configuration directory.
Create ~/.config/containers/systemd/endura-team-server.container:
[Unit]
Description=Endura Team Server
Wants=network-online.target
After=network-online.target
[Container]
Image=ghcr.io/endurasecurity/container/endura-team-server:testing
ContainerName=endura-team-server
EnvironmentFile=/absolute/path/to/team-server/.env
Volume=/absolute/path/to/team-server/config:/app/config:ro,Z
PublishPort=5150:5150
UserNS=keep-id
[Service]
Restart=always
[Install]
WantedBy=default.targetStop a manually started container with the same name gracefully, remove it, then start the service:
podman stop --time 60 endura-team-server
podman rm endura-team-server
systemctl --user daemon-reload
systemctl --user start endura-team-server.service
systemctl --user status endura-team-server.serviceThe [Install] section activates the generated service at user-manager startup. Quadlet services are generated units; do not run systemctl enable on them. Lingering starts the user manager at boot.
View service logs with journalctl --user -u endura-team-server.service -f.
Configure HTTPS
By default, forward public HTTPS through a trusted proxy to HTTP port 5150. Set SERVER_URL to the public HTTPS origin; keep the internal HTTP readiness check.
Direct TLS Termination
To terminate TLS in Team Server instead, configure:
settings:
tls:
certificate: "/run/secrets/server_cert"
private_key: "/run/secrets/server_key"Mount the certificate and key into the Team Server container:
-v /absolute/path/server.pem:/run/secrets/server_cert:ro,Z
-v /absolute/path/server-key.pem:/run/secrets/server_key:ro,ZAdd the mounts under volumes for Compose or as Volume= entries for Quadlet.
Direct TLS replaces HTTP on port 5150. Set SERVER_URL=https://team-server.example.com:5150, with matching OIDC callbacks, CORS, and tenant URL. If a front end maps public 443 to this listener, use its origin instead.
Remove the HTTP-only Compose healthcheck. Monitor public HTTPS /_readiness with certificate validation and use HTTPS for subsequent checks.
Recreate the Compose container after changing .env, certificate mounts, and configuration. For Quadlet, reload the edited container definition before restarting:
systemctl --user daemon-reload
systemctl --user restart endura-team-server.serviceVerify the public endpoint:
curl -f https://team-server.example.com:5150/_readinessUse the port-443 URL if applicable. For a private CA, pass --cacert /path/to/ca.pem; reserve -k for temporary self-signed testing.
Set Up the First Administrator
Sign in to create the unscoped Viewer record, then find it by OIDC email or built-in <ENDURA_AUTH_USERNAME>@localhost (for example, developer@localhost):
podman compose -f podman-compose.yaml exec endura-team-server \
endura task user_get email:admin@example.comFor direct Podman, replace the Compose prefix with podman exec endura-team-server.
Read the numeric ID from the response’s data, then assign the Administrator role:
podman compose -f podman-compose.yaml exec endura-team-server \
endura task user_update id:42 role:administratorRefresh the browser or sign in again.
Operate the Deployment
Logs and Status
podman compose -f podman-compose.yaml ps
podman compose -f podman-compose.yaml logs -f endura-team-server
podman compose -f podman-compose.yaml logs -f postgresFor Quadlet, use systemctl --user status endura-team-server and journalctl --user -u endura-team-server -f.
Update
Review Runtime Sensor compatibility before a major update.
Before upgrading, set ENDURA_CREDENTIAL_ENCRYPTION_KEY and check the plaintext credential blocker; adding a key does not convert old credentials.
Podman Compose:
podman compose -f podman-compose.yaml pull endura-team-server
podman compose -f podman-compose.yaml up -d endura-team-serverQuadlet:
podman pull ghcr.io/endurasecurity/container/endura-team-server:testing
systemctl --user restart endura-team-server.serviceTo rotate the credential-encryption key for either deployment method:
- Generate a new value with
openssl rand -base64 32. - In
.env, move the old value toENDURA_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYSand setENDURA_CREDENTIAL_ENCRYPTION_KEYto the new value. - Apply the changed environment file with
podman compose -f podman-compose.yaml up -d endura-team-serverorsystemctl --user restart endura-team-server.servicefor Quadlet. - Confirm that the logs report
credentials validatedand that/_readinesssucceeds. - Remove
ENDURA_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS, restart again, and repeat the validation checks.
Keep the previous key until restart succeeds. JWT rotation separately revokes tokens signed with the old secret.
Back Up and Restore
podman compose -f podman-compose.yaml exec -T postgres \
sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > team_server.sql
tar -czf team_server_config.tar.gz config/Back up source secrets (or .env) separately in protected storage. Restoring encrypted credentials requires the backup’s current encryption key and any previous keys then active.
Restore only into an empty or intentionally replaced database:
podman compose -f podman-compose.yaml exec -T postgres \
sh -c 'psql -U "$POSTGRES_USER" "$POSTGRES_DB"' < team_server.sqlUse the database provider’s backup procedure when PostgreSQL is external.
Follow Maintenance and Recovery for archive verification, restore sequencing, and post-restore checks.
Uninstall
Stop Compose services while preserving their database volume:
podman compose -f podman-compose.yaml downFor a direct container, run podman stop --time 60 endura-team-server, then podman rm endura-team-server. For Quadlet, stop the service and remove its source definition:
systemctl --user stop endura-team-server.service
rm ~/.config/containers/systemd/endura-team-server.container
systemctl --user daemon-reloadDeleting the Compose volume is permanent
podman compose -f podman-compose.yaml down --volumes deletes PostgreSQL and all Team Server data. Back up the database and confirm the project directory before running it.
Troubleshooting
Container Does Not Start
podman compose -f podman-compose.yaml config
podman compose -f podman-compose.yaml logs endura-team-serverCheck the configuration mount, referenced environment variables, credential-encryption key, and the two JWT secrets. An unavailable previous key or a credential-authentication failure also prevents startup.
Database Connection Fails
podman compose -f podman-compose.yaml logs postgres
podman compose -f podman-compose.yaml exec postgres \
sh -c 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'Compose uses the database host postgres. For an external database, confirm DNS, port 5432, TLS, and credentials from the Team Server container.
Rootless Service Stops After Logout
loginctl show-user "$USER" -p Linger
systemctl --user status endura-team-server.serviceEnable lingering and use Quadlet for a persistent rootless direct deployment.
SELinux Denies a Mount
Use :Z on private bind mounts and review denials with ausearch -m AVC -ts recent.
Internal OIDC or Integration Endpoint Is Blocked
Configure settings.http.allowed_networks and, when the identity provider or integration endpoint uses an internal CA, settings.http.ca_certificates. See Outbound HTTP Policy.
For additional help, use the Endura contact page.