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
相关产品推荐
相关产品推荐

