Portainer had become one of those tools in Realm Labs that I used constantly but rarely thought about.
It sat there managing Docker, giving me a clean web interface for containers, images, volumes, networks and stacks.
Then one day I realised the Portainer version was getting old.
Updating it sounded simple enough.
Pull the new image.
Replace the container.
Carry on.
But there was one fairly important question first:
What happens to everything Portainer is already managing?
I had multiple Docker containers running on the Raspberry Pi and didn’t particularly fancy turning a routine update into a complete rebuild.
The good news is that Portainer itself doesn’t own your Docker containers.
Once I understood that distinction, upgrading became much less intimidating.
The Concern
My initial assumption was that deleting the Portainer container might also remove the containers it managed.
That would have been a problem.
At this point the Docker host already had several services running, and Portainer was the main interface I used to manage them.
The setup looked roughly like:
Raspberry Pi
|
v
Docker Engine
|
+-- Container A
+-- Container B
+-- Container C
+-- Portainer
Portainer could see and manage those containers.
But it was important to understand that the actual Docker Engine was responsible for running them.
Portainer was only the management layer.
Portainer Is Not Docker
This is the key point.
The architecture is really:
Web Browser
|
v
Portainer
|
v
Docker Socket
|
v
Docker Engine
|
+-- Containers
+-- Images
+-- Networks
+-- Volumes
Portainer talks to Docker through the Docker socket.
Usually:
/var/run/docker.sock
The containers themselves belong to Docker.
So removing the Portainer container does not automatically remove:
- Other containers
- Docker images
- Volumes
- Networks
- Bind mounts
That made the update considerably safer than I’d first assumed.
What Portainer Does Need to Keep
There is one thing that does matter.
Portainer has its own configuration data.
That includes things such as:
- Users
- Portainer settings
- Endpoint configuration
- Saved environment information
- Portainer-specific metadata
That data normally lives in a Docker volume.
A typical Portainer deployment uses something like:
portainer_data
mounted inside the container as:
/data
So the important part wasn’t preserving the Portainer container.
It was preserving the Portainer data volume.
Checking the Existing Container
Before changing anything, I checked the current Portainer container.
A useful command is:
docker ps
This shows running containers.
For more detail:
docker inspect portainer
The exact container name may vary, but I wanted to confirm:
- Which image it was using
- Which ports were exposed
- Which volumes were mounted
- Whether the Docker socket was mounted
- The restart policy
A typical configuration might contain:
/var/run/docker.sock:/var/run/docker.sock
portainer_data:/data
Those are the two most important mounts.
Checking the Portainer Volume
I also checked that the data volume existed.
docker volume ls
This might show:
DRIVER VOLUME NAME
local portainer_data
You can inspect it with:
docker volume inspect portainer_data
As long as that volume remains intact, the Portainer configuration can be reused by the replacement container.
Back It Up Anyway
Even though the update process is straightforward, I still prefer having something to fall back on.
Before changing Portainer, I made sure I knew exactly how it had been deployed.
At minimum, I wanted a copy of the existing docker run command or Compose configuration.
If you’re using Docker Compose, keep the Compose file somewhere safe.
For example:
services:
portainer:
image: portainer/portainer-ce:latest
container_name: portainer
restart: unless-stopped
ports:
- "9443:9443"
- "8000:8000"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer_data:/data
volumes:
portainer_data:
With that stored safely, recreating Portainer is trivial.
Do Not Delete the Data Volume
This is the part that matters.
Removing the container is fine.
Removing the volume is not.
There is a big difference between:
docker rm portainer
and deleting:
portainer_data
Similarly, be careful with commands that aggressively remove unused Docker resources.
For example:
docker system prune
is useful, but I wouldn’t start throwing prune commands around during an update unless I understood exactly what they were going to remove.
The goal here was a controlled replacement.
Not spring cleaning.
Stop Portainer
The first actual update step was to stop the existing container.
docker stop portainer
That stops only Portainer.
The other containers continue running.
This is a useful moment to prove the architecture to yourself.
Run:
docker ps
and you’ll see the rest of the services are still alive.
Portainer disappearing doesn’t stop Docker.
Remove the Old Portainer Container
Once it was stopped:
docker rm portainer
Again, this removes the Portainer container itself.
It does not remove the existing Docker workloads.
It also does not remove the named portainer_data volume.
At this point Portainer is gone, but Docker is still happily running everything else.
Conceptually:
Before:
Docker
├── App 1
├── App 2
├── App 3
└── Portainer
After removing Portainer:
Docker
├── App 1
├── App 2
└── App 3
Nothing dramatic happens.
You just temporarily lose the web management interface.
Pull the New Portainer Image
Next I downloaded the newer image.
For Portainer Community Edition:
docker pull portainer/portainer-ce:latest
Docker downloads the current image while leaving the existing applications alone.
You can check downloaded images with:
docker images
Recreate Portainer
The next step is to create a new container using the same settings as before.
For example:
docker run -d \
-p 8000:8000 \
-p 9443:9443 \
--name portainer \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
portainer/portainer-ce:latest
The crucial part is:
-v portainer_data:/data
That reconnects the new Portainer container to the existing configuration.
And:
-v /var/run/docker.sock:/var/run/docker.sock
allows it to see the existing Docker environment.
Opening Portainer Again
Once the replacement container was running, I could open the normal Portainer address in the browser.
For example:
https://<docker-host>:9443
The existing environment appeared again.
The containers were still there.
The stacks were still there.
The networks and volumes were still there.
From the user’s perspective, Portainer had simply been upgraded.
Why the Existing Containers Survived
The easiest way to understand this is to separate the layers.
Portainer is:
Management
Docker Engine is:
Runtime
Your application containers are:
Workloads
So the relationship is:
Portainer
|
v
Docker Engine
|
v
Containers
Removing the top layer doesn’t remove the layers underneath.
It’s similar to closing a management console on another system.
The thing you’re managing continues to exist.
Updating with Docker Compose
If Portainer is deployed using Compose, the process can be even easier.
First:
docker compose pull
Then:
docker compose up -d
Docker Compose detects the newer image and recreates the Portainer container while retaining the existing volume configuration.
A typical workflow becomes:
Pull image
|
v
Recreate Portainer
|
v
Reconnect portainer_data
|
v
Existing environment returns
This is one reason I now prefer keeping important services defined in Compose files rather than relying purely on long docker run commands.
Check the Logs
After the upgrade, it’s worth checking that Portainer started cleanly.
docker logs portainer
If everything looks normal, you can also confirm:
docker ps
The container should show as running and expose the expected ports.
What About the Old Image?
After confirming the new version works, the old Portainer image can be removed if it is no longer required.
First check:
docker images
Then remove a specific unused image with:
docker rmi <IMAGE_ID>
There is no need to rush this step.
I’d rather leave an unused image around temporarily than aggressively clean things up before I’ve confirmed the update is stable.
A More Important Lesson About Persistent Data
This exercise reinforced one of the most important Docker concepts.
Containers should be disposable.
Data should not be.
A good Docker deployment separates:
Application container
from:
Persistent data
So when the application container is upgraded or rebuilt:
Old container
|
X
Removed
Persistent volume
|
v
New container
The application comes back with its previous data.
Portainer is a perfect example of this.
Named Volumes vs Bind Mounts
Portainer commonly uses a named volume:
portainer_data
Other applications might use bind mounts instead.
For example:
/home/user/docker/app/config:/config
The principle is the same.
If you destroy and recreate the application container but keep:
/home/user/docker/app/config
the new container can reuse the old configuration.
This is why I now pay much more attention to where application data actually lives before updating anything.
What I Check Before Updating Any Docker Container
The Portainer update gave me a simple process that works for other Docker services too.
Before upgrading anything, I check:
What image is currently running?
docker inspect <container>
Where is its persistent data?
Look for:
Volumes
Bind mounts
Named volumes
What ports does it expose?
For example:
9443:9443
Does it have environment variables?
These often include passwords, database addresses and application settings.
What is the restart policy?
Such as:
unless-stopped
or:
always
Can I recreate it?
If the answer is no, I shouldn’t be deleting it yet.
Don’t Use Portainer as the Only Record of Your Configuration
There is an interesting catch with managing Docker exclusively through a graphical interface.
If Portainer itself disappears and you don’t remember how something was configured, troubleshooting becomes much harder.
That’s why I increasingly prefer storing stack definitions as Compose files.
Instead of relying on memory, you have something like:
docker-compose.yml
which becomes the documentation for the service.
That file can be:
- Backed up
- Copied
- Version controlled
- Edited
- Reused after a rebuild
Portainer then becomes the convenient management layer rather than the only place where the configuration exists.
Testing Everything Afterwards
Once Portainer was upgraded, I checked the obvious things.
First:
docker ps
All expected containers should still be running.
Then I opened Portainer and confirmed that:
- Containers were visible
- Stacks were visible
- Volumes were visible
- Networks were visible
- Container logs could be opened
- Start/stop controls worked
Finally, I opened a couple of the applications themselves.
That confirmed that the Portainer update hadn’t affected the services it was managing.
What Could Actually Go Wrong?
Although replacing Portainer is relatively safe, there are still a few mistakes that could make life harder.
Deleting portainer_data
This removes Portainer’s persistent configuration.
Changing the Docker socket mount
Without:
/var/run/docker.sock:/var/run/docker.sock
Portainer won’t be able to manage the local Docker engine in the same way.
Changing ports unintentionally
If the new container exposes a different management port, the old URL will stop working.
Forgetting environment or restart settings
Recreating a container with different options can change its behaviour.
Using an incompatible upgrade path
For a major application upgrade, it’s always worth checking the application’s release notes first.
A Safer Update Pattern
My preferred pattern for Docker services now looks like this:
Check existing configuration
|
v
Confirm persistent storage
|
v
Back up important data
|
v
Pull new image
|
v
Stop old container
|
v
Remove old container
|
v
Create replacement
|
v
Test application
|
v
Only then clean up old image
It’s slower than clicking random update buttons.
But it’s predictable.
The Realm Labs Takeaway
The thing I was worried about was:
"If I remove Portainer, will I lose all my containers?"
The answer was no.
The real structure is:
Docker Engine
|
+-- Application containers
|
+-- Networks
|
+-- Volumes
|
+-- Portainer
Portainer is simply another container using the Docker API to manage the others.
As long as its persistent data is preserved, replacing Portainer itself is relatively painless.
The update process ended up being:
Stop Portainer
|
v
Remove Portainer container
|
v
Pull new image
|
v
Recreate using same volume
|
v
Everything returns
No application rebuilds.
No lost containers.
No lost volumes.
And no reason to put off updating Portainer simply because it happens to be the tool managing everything else.
Sometimes understanding where the state actually lives makes an apparently risky update remarkably straightforward.

