coollabsio/coolify · error · RuntimeException

No DigitalOcean token found for the server team.

Error message

No DigitalOcean token found for the server team.

What it means

DigitalOcean twin of the Vultr error: DeleteServer::deleteFromDigitalOceanById() throws this RuntimeException when deleteFromDigitalOcean=true (or the server has digitalocean_droplet_id with provider deletion enabled) and no CloudProviderToken with provider='digitalocean' can be resolved — first via the passed cloudProviderTokenId, then via any team-owned DigitalOcean token. The catch block logs and re-throws, so the Coolify-side server deletion (forceDelete) never runs.

Source

Thrown at app/Actions/Server/DeleteServer.php:165

    {
        try {
            $token = null;

            if ($cloudProviderTokenId) {
                $token = CloudProviderToken::where('id', $cloudProviderTokenId)
                    ->where('team_id', $teamId)
                    ->where('provider', 'digitalocean')
                    ->first();
            }

            if (! $token) {
                $token = CloudProviderToken::where('team_id', $teamId)
                    ->where('provider', 'digitalocean')
                    ->first();
            }

            if (! $token) {
                throw new \RuntimeException('No DigitalOcean token found for the server team.');
            }

            $digitalOceanService = new DigitalOceanService($token->token);
            $digitalOceanService->deleteDroplet($digitalOceanDropletId);

            logger()->debug('Deleted droplet from DigitalOcean', [
                'digitalocean_droplet_id' => $digitalOceanDropletId,
                'team_id' => $teamId,
            ]);
        } catch (\Throwable $e) {
            logger()->error('Failed to delete droplet from DigitalOcean', [
                'error' => $e->getMessage(),
                'digitalocean_droplet_id' => $digitalOceanDropletId,
                'team_id' => $teamId,
            ]);

            throw $e;
        }

View on GitHub (pinned to 70b9acc424)

Solutions

  1. Re-add a DigitalOcean provider token for the server's team so the fallback token lookup succeeds.
  2. Pass an explicit, valid cloudProviderTokenId (provider='digitalocean', matching team_id) when dispatching DeleteServer.
  3. If the droplet was already destroyed manually, delete the server without deleteFromDigitalOcean.
  4. Programmatically: catch the RuntimeException (log + notify) and continue Coolify-side deletion if provider cleanup is best-effort.

Example fix

// before
DeleteServer::run($serverId, deleteFromDigitalOcean: true,
    digitalOceanDropletId: $server->digitalocean_droplet_id);

// after
$hasToken = CloudProviderToken::where('team_id', $server->team_id)
    ->where('provider', 'digitalocean')->exists();
DeleteServer::run($serverId,
    deleteFromDigitalOcean: $hasToken,
    digitalOceanDropletId: $server->digitalocean_droplet_id);
Defensive patterns

Strategy: validation

Validate before calling

$hasDoToken = \App\Models\CloudProviderToken::query()
    ->where('team_id', $server->team_id)
    ->where('provider', 'digitalocean')
    ->exists();
if (! $hasDoToken) {
    return back()->withErrors(['server' => 'Add a DigitalOcean token for this team before deleting the server from DigitalOcean.']);
}
\App\Actions\Server\DeleteServer::run($server->id, deleteFromDigitalOcean: true, digitalOceanDropletId: $server->digitalocean_droplet_id);

Type guard

function teamHasDigitalOceanToken(int $teamId): bool
{
    return \App\Models\CloudProviderToken::query()
        ->where('team_id', $teamId)
        ->where('provider', 'digitalocean')
        ->exists();
}

Try / catch

try {
    DeleteServer::run($serverId, deleteFromDigitalOcean: true, digitalOceanDropletId: $dropletId);
} catch (\RuntimeException $e) {
    if (str_contains($e->getMessage(), 'No DigitalOcean token found')) {
        report($e); // ask user to re-link token; do not auto-retry
    } else {
        throw $e;
    }
}

Prevention

When it happens

Trigger: Deleting a DigitalOcean-linked server when the team's DO token was deleted, when cloudProviderTokenId references a token of the wrong provider or wrong team, or when the server row's team_id differs from the token owner after re-organization.

Common situations: DO token rotated/deleted after droplet creation; multiple teams where the token lives in another team; API automation passing deleteFromDigitalOcean without a token id.

Related errors


AI-assisted analysis of coollabsio/coolify@70b9acc424 (2026-08-17). Data as JSON: /api/errors/f15803157b9bba20. Report an issue: GitHub.