You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python调用Portainer API创建Stack报Invalid stack name错误

问题根因

接口返回Invalid stack name是代码里3个核心错误导致的:

  • Authorization请求头格式错误:Portainer要求鉴权头格式为Bearer <JWT令牌>,原代码直接传入token字符串,没有加Bearer 前缀
  • 手动设置了Content-Type: multipart/form-data请求头:使用requests上传文件时,不能手动指定这个头,requests会自动生成带boundary边界参数的合法Content-Type,手动设置会导致边界参数丢失,后端无法解析表单字段,读不到传入的Name参数
  • 类方法内变量引用错误:removeStack、getStacks方法直接调用全局变量url,没有使用实例属性self.url,同时创建栈的表单缺失curl里传入的EndpointID字段
  • 额外逻辑瑕疵:类初始化时就调用getStacks()查询栈ID,第一次创建栈时栈还不存在,会打印无意义的报错
修复后可运行代码
import requests
import json

class PortainerAPI:
    def __init__(self,url,stackName, endpoint_id=2):
        self.url = url
        self.stackName = stackName
        self.endpoint_id = endpoint_id
        self.bearerToken = self.authorize()
        self.stackID = None
        
    # 授权获取Bearer Token
    def authorize(self):
        payload = json.dumps({
          "password": "pwd_Admin",
          "username": "mes"
        })
        headers = {
          'Content-Type': 'application/json',
          'Accept': 'application/json'
        }
        response = requests.post(self.url+'/auth', headers=headers, data=payload)
        response.raise_for_status()
        jwt = response.json()["jwt"]
        return f"Bearer {jwt}"

    # 创建新栈
    def createStack(self, compose_file_path='docker-compose-apache.yml'):
        form_data = {
            'Name': self.stackName,
            'EndpointID': str(self.endpoint_id)
        }
        files = {
          'file': open(compose_file_path,'rb')
        }
        headers = {
          'Accept': 'application/json',
          'Authorization': self.bearerToken
        }

        response = requests.post(
            f"{self.url}/stacks?type=2&method=file&endpointId={self.endpoint_id}", 
            headers=headers, 
            data=form_data, 
            files=files
        )
        files['file'].close()
        return response.text
    
    # 删除栈
    def removeStack(self):
        if not self.stackID:
            self.getStacks()
        headers = {
          'Authorization': self.bearerToken
        }
        response = requests.delete(
            f"{self.url}/stacks/{self.stackID}?external=true&endpointId={self.endpoint_id}", 
            headers=headers
        )
        print(response.text)
    
    # 获取栈ID
    def getStacks(self):
        headers = {
          'Accept': '*/*',
          'Authorization': self.bearerToken
        }
        response = requests.get(f"{self.url}/stacks", headers=headers)
        response.raise_for_status()
        data = response.json()
        try:
            stackObject = list(filter(lambda x:x["Name"]==self.stackName,data))
            self.stackID = stackObject[0]['Id']
            return self.stackID
        except IndexError:
            print(f"未找到名为{self.stackName}的栈")
            return None
        
        
if __name__=='__main__':
    url = "http://XX.XXX.XXX.X:9000/api"
    stackName = 'test'
    
    test = PortainerAPI(url,stackName)
    print(test.createStack())
关键修改说明
  • 补全Authorization头的Bearer 前缀,符合Portainer接口鉴权规范
  • 删除手动设置的multipart/form-data请求头,让requests自动生成带合法boundary的请求头,保证后端可以正常解析表单参数
  • 补全创建栈时表单里的EndpointID字段,和可正常运行的curl请求参数完全对齐
  • 修复类内部的变量引用,所有接口地址统一使用实例属性self.url、self.endpoint_id,避免全局变量引用错误
  • 调整初始化逻辑,不再初始化时强制查询栈ID,避免首次创建栈时打印无意义的报错
  • 增加接口状态码检查、文件句柄关闭逻辑,降低排查成本和资源泄漏风险

内容的提问来源于stack exchange,提问作者Robin Laegler

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 11:15:42