How to Migrate from Portainer to usulnet: Step-by-Step Guide
Portainer has been a popular Docker management UI for years, and many teams started their container management journey with it. But as infrastructure grows and requirements evolve, some teams find themselves needing a different approach. Whether you are hitting Portainer's limitations with multi-host management, frustrated by the feature restrictions in the Community Edition, or simply looking for a self-hosted solution with a more modern interface, migrating to usulnet is a straightforward process.
This guide walks you through the complete migration process: from auditing your current Portainer setup, through exporting your configurations, to deploying and validating everything in usulnet. The key insight is that your containers and Docker resources exist independently of any management tool. Neither Portainer nor usulnet owns your containers — they just provide an interface to manage them.
Why Teams Migrate from Portainer
Before diving into the technical process, it helps to understand the common reasons teams make this switch:
- Licensing limitations: Portainer's Business Edition requires a paid license for features like role-based access control, registry management, and advanced deployment options. The free Community Edition (CE) is increasingly feature-restricted with each release.
- Multi-host complexity: Managing multiple Docker hosts in Portainer requires configuring agents on each host and dealing with endpoint management overhead.
- Performance at scale: Portainer's UI can become sluggish when managing many containers across multiple environments.
- Self-hosted philosophy: usulnet is built from the ground up as a self-hosted platform with no telemetry, no license restrictions, and complete data ownership.
- Modern interface: usulnet provides a clean, fast dashboard designed for modern workflows, with better real-time monitoring and container management capabilities.
Pre-Migration Checklist
Before you begin, gather information about your current Portainer setup. This audit ensures nothing gets missed during the transition.
Step 1: Inventory Your Portainer Resources
Log into Portainer and document the following:
- Environments/Endpoints: List all Docker hosts connected to Portainer. Note the connection type (local socket, agent, or Edge agent) and the host address.
- Stacks: For each stack, record the name, the Docker Compose content, and any environment variables. Navigate to Stacks and export each compose file.
- Standalone containers: Document any containers that were created outside of stacks, including their image, ports, volumes, environment variables, and restart policy.
- Networks: List custom networks, their drivers, and subnets.
- Volumes: List named volumes and their drivers.
- Registries: Note any private registries configured in Portainer with their URLs and credentials.
- Users and teams: If using Portainer Business Edition, document user accounts and their access levels.
Step 2: Export Stacks from Portainer
The most important data to preserve are your stack definitions. Portainer stores stacks as Docker Compose files internally, so exporting them is straightforward:
# Method 1: Export via Portainer UI
# Navigate to Stacks > [Stack Name] > Editor
# Copy the compose file content and save to a local file
# Repeat for each stack
# Method 2: Export via Portainer API
# Get your API token first
PORTAINER_URL="https://your-portainer:9443"
TOKEN=$(curl -s -X POST "$PORTAINER_URL/api/auth" \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"your-password"}' | jq -r '.jwt')
# List all stacks
curl -s -H "Authorization: Bearer $TOKEN" \
"$PORTAINER_URL/api/stacks" | jq '.[] | {id, name, type}'
# Export each stack's compose file
STACK_ID=1
curl -s -H "Authorization: Bearer $TOKEN" \
"$PORTAINER_URL/api/stacks/$STACK_ID/file" | jq -r '.StackFileContent' \
> stack-name.yml
For a bulk export of all stacks:
#!/bin/bash
# export-portainer-stacks.sh
PORTAINER_URL="https://your-portainer:9443"
TOKEN=$(curl -s -X POST "$PORTAINER_URL/api/auth" \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"your-password"}' | jq -r '.jwt')
mkdir -p portainer-export
curl -s -H "Authorization: Bearer $TOKEN" "$PORTAINER_URL/api/stacks" | \
jq -c '.[]' | while read -r stack; do
ID=$(echo "$stack" | jq -r '.Id')
NAME=$(echo "$stack" | jq -r '.Name')
echo "Exporting stack: $NAME (ID: $ID)"
# Export compose file
curl -s -H "Authorization: Bearer $TOKEN" \
"$PORTAINER_URL/api/stacks/$ID/file" | \
jq -r '.StackFileContent' > "portainer-export/${NAME}.yml"
# Export environment variables
echo "$stack" | jq '.Env' > "portainer-export/${NAME}.env.json"
done
echo "Export complete. Files saved to portainer-export/"
Step 3: Document Standalone Containers
Containers not managed by stacks need manual documentation. You can extract their configuration directly from Docker:
# List all containers with their creation method
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}"
# Generate a docker run command from an existing container
# Using docker inspect to reconstruct the configuration
docker inspect mycontainer | jq '.[0] | {
Image: .Config.Image,
Ports: .HostConfig.PortBindings,
Volumes: .HostConfig.Binds,
Env: .Config.Env,
RestartPolicy: .HostConfig.RestartPolicy,
Networks: (.NetworkSettings.Networks | keys)
}'
Tip: Consider converting standalone containers to Docker Compose stacks during the migration. This is a good opportunity to adopt infrastructure-as-code practices. Write a
docker-compose.ymlfile for each logical group of containers.
Installing usulnet
With your inventory complete, it is time to deploy usulnet. The installation is designed to be simple and non-disruptive. usulnet runs as a Docker container itself, so it won't interfere with your existing containers.
# Pull and run usulnet
docker run -d \
--name usulnet \
--restart always \
-p 8443:8443 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v usulnet_data:/data \
usulnet/usulnet:latest
After starting usulnet, open your browser and navigate to https://your-server:8443 to complete the initial setup. Create your admin account and configure your first Docker environment.
Feature Mapping: Portainer to usulnet
Understanding how Portainer concepts map to usulnet features helps you navigate the new interface quickly:
| Portainer Feature | usulnet Equivalent | Notes |
|---|---|---|
| Environments/Endpoints | Docker Hosts | Connect via socket or remote API |
| Stacks | Compose Stacks | Full Docker Compose v2 support |
| Container list | Containers dashboard | Real-time status with health monitoring |
| Container console | Terminal access | Web-based terminal into containers |
| Container logs | Log viewer | Real-time log streaming with search |
| Images | Image management | Pull, tag, remove, and inspect images |
| Networks | Network management | Create, inspect, and manage networks |
| Volumes | Volume management | Create, inspect, and browse volumes |
| Registries | Registry configuration | Connect private registries |
| App Templates | Quick Deploy templates | One-click deployment of common apps |
| Edge Agent | Remote host agent | Manage hosts behind firewalls |
Step-by-Step Migration Process
Step 1: Connect Your Docker Hosts
In usulnet, add each Docker host that was previously managed by Portainer. For the local host where usulnet is running, the Docker socket is already connected. For remote hosts:
# On each remote Docker host, ensure the Docker API is accessible
# Option 1: TCP socket (configure TLS for production)
# Edit /etc/docker/daemon.json on the remote host
{
"hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2376"],
"tls": true,
"tlscacert": "/etc/docker/certs/ca.pem",
"tlscert": "/etc/docker/certs/server-cert.pem",
"tlskey": "/etc/docker/certs/server-key.pem",
"tlsverify": true
}
Then add the remote host in usulnet's dashboard by providing the host address and TLS certificates.
Step 2: Import Your Stacks
Take the compose files you exported from Portainer and deploy them through usulnet. Your existing containers will continue running undisturbed because the stacks reference the same Docker resources.
# Your exported compose files can be deployed directly
# Example: portainer-export/myapp.yml
services:
web:
image: nginx:alpine
ports:
- "80:80"
volumes:
- web_data:/usr/share/nginx/html
restart: always
api:
image: myapp-api:latest
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://user:pass@db:5432/mydb
depends_on:
- db
db:
image: postgres:16
volumes:
- db_data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=secret
volumes:
web_data:
db_data:
In usulnet, navigate to the Compose Stacks section and create a new stack by pasting the compose content. If the stack references existing volumes and networks, usulnet will recognize and use them rather than creating duplicates.
Step 3: Configure Registries
If you had private registries configured in Portainer, add them to usulnet. Navigate to the registry settings and provide the URL and authentication credentials for each private registry.
Step 4: Set Up Monitoring
One of the advantages of usulnet is its built-in monitoring. Once your hosts and stacks are connected, the dashboard immediately shows container health status, resource usage, and logs. Configure any alerting or notification preferences at this stage.
Step 5: Validate Everything
Run through this checklist to confirm the migration is complete:
- All Docker hosts are connected and showing as online
- All containers are visible and showing correct status
- Stack definitions match your exported Portainer stacks
- You can view logs for all containers
- You can exec into containers via the web terminal
- Private registry images can be pulled
- Resource monitoring is displaying metrics
- Network and volume management is working
Step 6: Remove Portainer
Once you have confirmed everything is working in usulnet, you can remove Portainer. Keep the data volume for a few weeks as a backup, just in case:
# Stop and remove the Portainer container
docker stop portainer
docker rm portainer
# Remove Portainer agent containers from remote hosts (if used)
docker stop portainer_agent
docker rm portainer_agent
# Keep the data volume as backup (remove later)
# docker volume rm portainer_data
# Clean up Portainer-specific networks if any
docker network ls --filter name=portainer -q | xargs docker network rm 2>/dev/null
Migration Tips and Common Issues
Handling Portainer-Managed Stacks
Portainer adds labels to containers it manages. These labels are harmless and can be left in place. usulnet does not conflict with them:
# Portainer labels you might see on containers
com.docker.compose.project: mystack
io.portainer.accesscontrol.users: admin
Environment Variables with Secrets
If you used Portainer's environment variable feature to inject secrets into stacks, make sure those values are captured during export. The API export method preserves environment variables as JSON, which you can convert to a .env file:
# Convert Portainer env export to .env format
cat portainer-export/myapp.env.json | jq -r '.[] | "\(.name)=\(.value)"' > myapp.env
Webhooks and Automation
If you had Portainer webhooks for automated redeployment, update your CI/CD pipelines to use usulnet's API endpoints instead. The specific endpoints will depend on your workflow, but the general pattern is similar: a POST request triggers a stack update or container restart.
Multiple Users
If your team had multiple user accounts in Portainer, recreate them in usulnet. Set up appropriate access levels and team assignments based on your original Portainer RBAC configuration.
Running Both During Transition
You can run Portainer and usulnet simultaneously during the transition period. They both connect to the same Docker socket and read the same container state. Neither tool locks or modifies container metadata in a way that conflicts with the other. This lets your team gradually shift to usulnet while keeping Portainer available as a fallback.
Tip: Schedule the migration during a maintenance window if possible, not because of downtime risk (there should be none), but because having team members explore usulnet together during a dedicated session accelerates adoption.
After Migration
Once you are settled into usulnet, take advantage of features that may not have been available in your Portainer edition. Explore the real-time monitoring dashboard, set up container health check alerting, and consider organizing your stacks and hosts for optimal visibility. The migration is also a good time to review and standardize your Docker Compose files, add health checks to services that lack them, and document your container infrastructure.
Migrating between Docker management tools is less disruptive than it appears, because the tools are an interface layer on top of Docker's existing state. Your containers, volumes, networks, and images remain exactly where they are. You are simply changing the window through which you view and manage them.