Encontrei uma solução adaptando este exemplo DQL para DBAL. O truque é obter o SQL bruto da subconsulta, envolvê-lo entre colchetes e juntá-lo. Os parâmetros usados na subconsulta devem ser definidos na consulta principal:
$subSelect = $connection->createQueryBuilder()
->select(array('userSurveyID', 'MIN(timestamp) timestamp'))
->from('user_survey_status_entries')
// Instead of setting the parameter in the main query below, it could be quoted here:
// ->where('status = ' . $connection->quote(UserSurveyStatus::ACCESSED))
->where('status = :status')
->groupBy('userSurveyID');
$select = $connection->createQueryBuilder()
->select($selectColNames)
->from('user_surveys', 'us')
// Get raw subquery SQL and wrap in brackets.
->leftJoin('us', sprintf('(%s)', $subSelect->getSQL()), 'firstAccess', 'us.userSurveyID = firstAccess.userSurveyID')
// Parameter used in subquery must be set in main query.
->setParameter('status', UserSurveyStatus::ACCESSED)
->where('us.surveyID = :surveyID')->setParameter('surveyID', $surveyID);