Skip to content

Instantly share code, notes, and snippets.

@deviantony
Last active July 7, 2024 01:37
Show Gist options
  • Save deviantony/77026d402366b4b43fa5918d41bc42f8 to your computer and use it in GitHub Desktop.
Save deviantony/77026d402366b4b43fa5918d41bc42f8 to your computer and use it in GitHub Desktop.
Portainer HTTP API by example

DEPRECATION NOTICE

This gist is now deprecated in favor of our official documentation: https://documentation.portainer.io/api/api-examples/ which contains up to date examples!

THE FOLLOWING DOCUMENTATION IS DEPRECATED

Please refer to the link above to get access to our updated API documentation and examples.

Introduction

This document presents a simple way to manage your Docker resource by using Portainer as a gateway (HTTP queries against the Portainer API).

The API documentation is available here: https://app.swaggerhub.com/apis/deviantony/portainer/

WARNING: This documentation is valid for Portainer >= 1.18.0.

NOTE: I'm using httpie to execute HTTP queries from the CLI.

Deploy a Portainer instance

# Note: I'm bind-mouting the Docker socket to be able to manage the local engine where Portainer is running.
# You can skip the bind-mount if you want to manage a remote environment.

$ docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer

Initialize the admin password

$ http POST :9000/api/users/admin/init Username="admin" Password="adminpassword"

Authenticate against the API using the admin account

$ http POST :9000/api/auth Username="admin" Password="adminpassword"

The response is a JSON object containing the JWT token inside the jwt field:

{
  "jwt":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsImV4cCI6MTQ5OTM3NjE1NH0.NJ6vE8FY1WG6jsRQzfMqeatJ4vh2TWAeeYfDhP71YEE"
}

You need to retrieve this token. You will need to pass this token inside the Authorization header when executing an authentication query against the API.

The value of the Authorization header must be of the form Bearer <JWT_TOKEN>.

Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsImV4cCI6MTQ5OTM3NjE1NH0.NJ6vE8FY1WG6jsRQzfMqeatJ4vh2TWAeeYfDhP71YEE

NOTE: This token has a 8 hour validity, you'll need to generate another token to execute authenticated queries once this one expires.

Create a new endpoint

Here, I'll show how to create 3 different types of endpoints:

  • Local endpoint using Docker socket communication
  • Remote endpoint using TCP communication
  • Remote endpoint using TCP communication secured via TLS

Local endpoint via the Docker socket

This query will create an endpoint called test-local and will use the Docker socket to communicate with this environment.

NOTE: This example requires to you bind-mount the Docker socket when running Portainer.

$ http --form POST :9000/api/endpoints \
"Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsImV4cCI6MTQ5OTM3NjE1NH0.NJ6vE8FY1WG6jsRQzfMqeatJ4vh2TWAeeYfDhP71YEE" \
Name="test-local" EndpointType=1

The response is a JSON object representing the endpoint:

{
    "AuthorizedTeams": [], 
    "AuthorizedUsers": [], 
    "Extensions": [], 
    "GroupId": 1, 
    "Id": 1, 
    "Name": "test-local", 
    "PublicURL": "",
    "Type": 1,
    "TLSConfig": {
        "TLS": false, 
        "TLSSkipVerify": false
    }, 
    "Type": 1, 
    "URL": "unix:///var/run/docker.sock"
}

Retrieve the value of the Id property, it will be used to execute queries against the Docker engine for that endpoint.

Remote endpoint

This query will create an endpoint called test-remote and will communicate with this environment over TCP using the IP address 10.0.7.10 and port 2375 (these are example values, ensure that you're using the correct IP & port).

NOTE: The Docker API must be exposed on that IP address & port. Please refer to the Docker documentation to check how to configure this.

$ http --form POST :9000/api/endpoints \
"Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsImV4cCI6MTQ5OTM3NjE1NH0.NJ6vE8FY1WG6jsRQzfMqeatJ4vh2TWAeeYfDhP71YEE" \
Name="test-remote" URL="tcp://10.0.7.10:2375" EndpointType=1

The response is a JSON object representing the endpoint:

{
    "AuthorizedTeams": [], 
    "AuthorizedUsers": [], 
    "Extensions": [], 
    "GroupId": 1, 
    "Id": 1, 
    "Type": 1,
    "Name": "test-remote", 
    "PublicURL": "", 
    "TLSConfig": {
        "TLS": false, 
        "TLSSkipVerify": false
    }, 
    "Type": 1, 
    "URL": "tcp://10.0.7.10:2375"
}

Retrieve the value of the Id property, it will be used to execute queries against the Docker engine for that endpoint.

Remote endpoint secured using TLS

This query will create an endpoint called test-remote-tls and will communicate with this environment over TCP (secured with TLS) using the IP address 10.0.7.10 and port 2376 (these are example values, ensure that you're using the correct IP & port).

NOTE: The Docker API must be exposed on that IP address & port. Please refer to the Docker documentation to check how to configure this.

$ http --form POST :9000/api/endpoints \
"Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsImV4cCI6MTQ5OTM3NjE1NH0.NJ6vE8FY1WG6jsRQzfMqeatJ4vh2TWAeeYfDhP71YEE" \
Name="test-remote" URL="tcp://10.0.7.10:2376" EndpointType=1 TLS="true" TLSCACertFile@/path/to/ca.pem TLSCertFile@/path/to/cert.pem TLSKeyFile@/path/to/key.pem

The response is a JSON object representing the endpoint:

{
    "AuthorizedTeams": [], 
    "AuthorizedUsers": [], 
    "Extensions": [], 
    "GroupId": 1, 
    "Id": 1, 
    "Type": 1,
    "Name": "test-remote", 
    "PublicURL": "", 
    "TLSConfig": {
        "TLS": true, 
        "TLSCACert": "/data/tls/1/ca.pem", 
        "TLSCert": "/data/tls/1/cert.pem", 
        "TLSKey": "/data/tls/1/key.pem", 
        "TLSSkipVerify": false
    }, 
    "Type": 1, 
    "URL": "tcp://10.0.7.10:2376"
}

Retrieve this ID, it will be used to execute queries against the Docker engine for that endpoint.

Execute Docker queries against a specific endpoint

By using the following Portainer HTTP API endpoint /api/endpoints/<ENDPOINT_ID>/docker, you can now execute any of the Docker HTTP API requests.

This Portainer HTTP API endpoint acts as a reverse-proxy to the Docker HTTP API.

NOTE: You can refer to the Docker API documentation to get more information on how you can query the Docker engine.

As an example, here is how you can list all the containers available in a specific endpoint:

$ http GET :9000/api/endpoints/1/docker/containers/json \
"Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsImV4cCI6MTQ5OTM3NjE1NH0.NJ6vE8FY1WG6jsRQzfMqeatJ4vh2TWAeeYfDhP71YEE" \
all==true

The response is exactly the same as returned by the ContainerList operation of the Docker API, see the documentation for the ContainerList operation.

Create a container

Here is how you can create a container in a specific endpoint using the Portainer HTTP API as a gateway.

This query will create a new Docker container inside the endpoint using the ID 1. The container will be named web01, use the nginx:latest Docker image and publish the container port 80 on via the 8080 port on the host.

See the link below to retrieve more information on how you can create a container using the Docker HTTP API.

$ http POST :9000/api/endpoints/1/docker/containers/create \
"Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsImV4cCI6MTQ5OTM3NjE1NH0.NJ6vE8FY1WG6jsRQzfMqeatJ4vh2TWAeeYfDhP71YEE" \
name=="web01" Image="nginx:latest" \
ExposedPorts:='{ "80/tcp": {} }' \
HostConfig:='{ "PortBindings": { "80/tcp": [{ "HostPort": "8080" }] } }'

The response is exactly the same as returned by the ContainerCreate operation of the Docker API, see the documentation for the ContainerCreate operation.

Example response:

{
    "Id": "5fc2a93d7a3d426a1c3937436697fc5e5343cc375226f6110283200bede3b107",
    "Warnings": null
}

Retrieve the ID of the container, you will need it to execute actions against that container.

Start a container

You can now start the container that you previously created using the endpoint /api/endpoints/<ENDPOINT_ID>/docker/containers/<CONTAINER_ID>/start (ensure you retrieved the ID of the container created previsouly):

$ http POST :9000/api/endpoints/1/docker/containers/5fc2a93d7a3d426a1c3937436697fc5e5343cc375226f6110283200bede3b107/start \
"Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsImV4cCI6MTQ5OTM3NjE1NH0.NJ6vE8FY1WG6jsRQzfMqeatJ4vh2TWAeeYfDhP71YEE"

The response is exactly the same as returned by the ContainerStart operation of the Docker API, see the documentation for the ContainerStart operation.

Delete a container

You can create a container using the following endpoint /api/endpoints/<ENDPOINT_ID>/docker/containers/<CONTAINER_ID>/remove:

$ http DELETE :9000/api/endpoints/1/docker/containers/5fc2a93d7a3d426a1c3937436697fc5e5343cc375226f6110283200bede3b107 \
"Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsImV4cCI6MTQ5OTM3NjE1NH0.NJ6vE8FY1WG6jsRQzfMqeatJ4vh2TWAeeYfDhP71YEE" \
force==true

The response is exactly the same as returned by the ContainerDelete operation of the Docker API, see the documentation for the ContainerDelete operation.

Manage Docker stacks in a Swarm environment

By using the following Portainer HTTP API endpoint /api/stacks, you can deploy and remove stack inside a specific Docker environment (Portainer endpoint).

In the following instructions, I'll assume that the endpoint using the ID 1 is connected to a Swarm cluster.

Before trying to create any stack, you need to retrieve the identifier of your Swarm cluster. It will be used to create stacks in the next steps.

$ http GET :9000/api/endpoints/1/docker/swarm \
"Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsImV4cCI6MTQ5OTM3NjE1NH0.NJ6vE8FY1WG6jsRQzfMqeatJ4vh2TWAeeYfDhP71YEE"

In the response of this query, you'll find the identifier of the Swarm cluster inside the ID property:

{
  "CreatedAt": "2018-06-05T15:03:30.226627346Z", 
  "ID": "k427pd86wfkgp40ksrmbojrwq",
}

Deploy a new stack from a public Git repository

This query will create a new stack inside the endpoint using the ID 1. The stack will be named myTestStack, use the Stack file stored in the public Git repository https://github.com/portainer/templates under the path stacks/cockroachdb/docker-stack.yml.

$ http POST ':9000/api/stacks?method=repository&type=1&endpointId=1' \
"Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsImV4cCI6MTUyOTY3NDE1Mn0.wyyck_iktvOrYsuH0Xrvgifoi83emsszipSTYkDvuaE" \
Name="myTestStack" \
SwarmID="k427pd86wfkgp40ksrmbojrwq" \
RepositoryURL="https://github.com/portainer/templates" \
ComposeFilePathInRepository="stacks/cockroachdb/docker-stack.yml"

The response is a JSON object representing the newly created stack:

{
    "EndpointId": 1, 
    "EntryPoint": "stacks/cockroachdb/docker-stack.yml", 
    "Env": null, 
    "Id": 1, 
    "Name": "myTestStack", 
    "ProjectPath": "/data/compose/\u0002", 
    "SwarmId": "tdv7rl1u3965ml272o2q6d96z", 
    "Type": 1
}

Retrieve the stack identifier in the Id property, you'll need it to manage the stack.

Remove an existing stack

This query will remove the existing stack with the identifier 1 inside the endpoint using the ID 1.

$ http DELETE :9000/api/stacks/1?endpointId=1 \
"Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsImV4cCI6MTQ5OTM3NjE1NH0.NJ6vE8FY1WG6jsRQzfMqeatJ4vh2TWAeeYfDhP71YEE"
@denzuko
Copy link

denzuko commented May 21, 2021

@deviantony sounds like @greenmind-sec's issue is English is not his first language so reading the docs might not be easy for him.

@greenmind-sec
Try adding pyswagger into your code and keep in mind that once your authenticated one can then talk to the underlining docker (swarm and engine APIs)[ https://docs.docker.com/engine/api/v1.30/#operation/ContainerCreate] under the /endpoints/{id}/docker Portainer API endpoint which is a pass through end point from portainer to the container's docker socket.

@deviantony
Copy link
Author

Just an update for everyone following this feed, the work on our side to make it up to date and put this inside our documentation has made some progress and I'll keep you posted here when it is available!

@lmjlopes
Copy link

lmjlopes commented May 23, 2021

Hi all,

I've successfully build some python code to start and stop containers using this API. But I've struggling without success to send commands to a running container. I didn't find anywhere an example to help me.
I'm trying to emulate in Python the behaviour of the Portainer page "Container Console", selecting "Use custom command".
Am I doing something wrong, missing something or is it not even possible?

`def get_container_exec(self):

    r=requests.post(self.portainer_endpoint+"/endpoints/2/docker/containers/" + self.portainer_container + "/exec", 
        headers={
            "Authorization": "Bearer {}".format(self.token),
            "Content-Type": "application/json"},
        json = {
            "Id": self.portainer_container,
            "AttachStderr": True,
            "AttachStdin": True,
            "AttachStdout": True,
            "Tty": True,
            "Cmd": ["python3", "test.py"]},
        verify=self.verifySSL,
        timeout = 5)`

@deviantony
Copy link
Author

@lmjopes container exec will be harder to automate, it is using the websocket protocol not classic REST HTTP API.

@lmjlopes
Copy link

@deviantony
Based on your response I rerouted this in another way. I managed sucessfully to do what I needed ( start, stop and send commands to a running container) using another container to act as a gateway, based on the container from "philhawthorne/ha-dockermon".
I was trying to make it simple and use just what I already had, Portainer.
Thanks anyway for your reply.

@filosof86
Copy link

filosof86 commented Jun 1, 2021

Hi @deviantony

Retrieving the user info (getting the list or the user's info), I do not see his password although the password field is described as the one which has to be returned https://app.swaggerhub.com/apis/deviantony/Portainer/2.0.1#/users/UserList

Parsed response

$VAR1 = { 
          'success' => 1,
          'response' => { 
                          'PortainerAuthorizations' => undef,
                          'Role' => 2,
                          'Id' => 2,
                          'EndpointAuthorizations' => undef,
                          'Username' => 'filosof'
                        },
          'return_code' => 200
        };

I just wanted to implement auto-login into the Portainer WI previously comparing the predefined list of the user names with the list defined in Portainer and if some username exists - take his credentials to log in.

Any chance for me to do that?

Thanks!

@deviantony
Copy link
Author

DEPRECATION NOTICE

This gist is now deprecated in favor of our official documentation: https://documentation.portainer.io/api/api-examples/ which contains up to date examples!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment