Mistakes are a great educator when one is honest enough to admit them and willing to learn from them, Aleksandr Solzhenitsynv
Portainer is a container management platform that provides a user-friendly interface for deploying, managing, and monitoring containerized applications —especially those running on Docker or Kubernetes.
This is the third post in our Portainer series, building on Deploying Portainer on Proxmox LXC. It is highly recommended that you read our first Deploying Portainer on Proxmox LXC: Script-Driven & GUI Walkthrough and second posts, Deploying WordPress as a Portainer Stack: A Hands-On Guide to Docker Compose.
Log into your Portainer Server instance by opening a web browser and going to: https://192.168.1.40:9443 (or your container’s IP).
In the Portainer interface, click on the Stacks menu item located on the left sidebar. Then, press the Add stack button at the top-right corner of the Stacks page.
Configure the Stack:
Name: Provide a name for your stack, e.g., mynginx
Compose File: You can either write your Docker Compose file in the provided editor or upload an existing file.
Environment Variables: Optionally, define any environment variables needed for your services.
Under Build method, choose Web editor. This lets you type out a Docker Compose file manually.
In the big text box, paste your entire docker-compose.yml
version: '3'
services:
nginx:
image: nginx:latest
ports:
- "8080:80"
volumes:
- /var/www/html:/usr/share/nginx/html
restart: always
version: ‘3’ It specifies the version of the Docker Compose file format.
services: It defines the different services (containers) that make up your application. In this case, there is one service named nginx.
nginx: This is the name of the service, which will be used to create a container for running Nginx.
image: nginx:latest This specifies the Docker image to use for the container. Here, it pulls the latest version of the official Nginx image from Docker Hub.
ports: This section maps ports from the host to the container:
- “8080:80” This maps port 8080 on the host machine to port 80 in the Nginx container. This means you can access the Nginx web server by visiting http://192.168.1.40:8080/ in a web browser.
volumes: It defines data volumes to persist data and share files between the host and the container:
- /var/www/html:/usr/share/nginx/html This mounts the host directory /var/www/html to the container directory /usr/share/nginx/html. Any files placed in /var/www/html on the host will be served by Nginx, allowing you to easily manage web content.
restart: always: This option ensures that the Nginx container automatically restarts if it stops or if the Docker daemon restarts. \
Creating the index.html file on the host server (where Docker and Portainer are installed):
mkdir -p /var/www/html
cd /var/www/html
nvim index.html
<!DOCTYPE html>
<html>
<head>
<title>Hello, Nginx!</title>
</head>
<body>
<h1>Hello, Nginx!</h1>
<p>This is a test page served by Nginx in a Docker container.</p>
</body>
</html>
Click Deploy the stack.
Access the Nginx web server by visiting http://192.168.1.40:8080/ in a web browser.
In Portainer, under the Stacks section, you can manage your containers efficiently. Here are the quick actions available for each container:
https://192.168.1.40:9443
, http://192.168.1.40:9000
.
# If Portainer UI hangs or times out:
docker restart portainer
docker stop portainer && docker rm portainer
docker volume rm portainer_data
docker volume create portainer_data
# Use the HTTP Port as a Fallback
# Portainer CE by default listens on 9443 for HTTPS.
# If you want an unencrypted HTTP interface on 9000, you must add --http-enabled when running
docker run -d -p 8000:8000 -p 9000:9000 -p 9443:9443 --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:latest --http-enabled
# Then, you can test
curl -I http://192.168.1.40:9000
docker logs portainer # inspect errors
pct enter ContainerID
, docker ps -a
(check container status), docker logs portainer
(check container logs).
# Inside your LXC container (myportainer)
pct enter 110
# Ensure the container’s IPv4 address really is 192.168.1.40/24
root@portainer:~# ip a show eth0
2: eth0@if17: mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether bc:24:11:39:28:4c brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.1.40/24 brd 192.168.1.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::be24:11ff:fe39:284c/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
root@portainer:~# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2b92263d8e90 wordpress:latest "docker-entrypoint.s…" 8 hours ago Up 8 hours 0.0.0.0:8880->80/tcp, [::]:8880->80/tcp mysql-wordpress-1
e5704349f155 mysql:8.0 "docker-entrypoint.s…" 8 hours ago Up 8 hours 3306/tcp, 33060/tcp mysql-db-1
bd7a95ca494c portainer/portainer-ce:latest "/portainer" 9 hours ago Up 9 hours 0.0.0.0:8000->8000/tcp, [::]:8000->8000/tcp, 0.0.0.0:9443->9443/tcp, [::]:9443->9443/tcp, 9000/tcp myportainer
3ec276d311bf hello-world "/hello" 9 hours ago Exited (0) 9 hours ago vibrant_wozniak
# Disable or Adjust UFW in the Container
root@portainer:~# sudo ufw status verbose
Status: inactive
# Otherwise, you can adjust UFW in the Container
sudo ufw allow 9443/tcp
sudo ufw reload
# or simply disable UFW
sudo ufw disable
# Test HTTPS interface locally
curl -k https://127.0.0.1:9443 -I
HTTP/1.1 200 OK
# Portainer is running correctly inside the container.
[...]
# Or test the HTTP fallback on 9000 if you have already enabled it
curl -I http://127.0.0.1:9000
sudo netstat -tuln | grep -E '9000|9443'
. It requires apt install net-tools
.
sudo apt install net-tools
# Confirm Portainer is listening on all interfaces (0.0.0.0:9443):
root@ubuntu-vpn:~# sudo netstat -tuln | grep -E '9000|9443'
tcp 0 0 0.0.0.0:9000 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:9443 0.0.0.0:* LISTEN
tcp6 0 0 :::9000 :::* LISTEN
tcp6 0 0 :::9443
cat /etc/pve/lxc/110.conf
arch: amd64
cores: 2
# The features line allows the container to run nested containers nested containers, meaning you can run LXC containers or Docker containers inside this container,
# and manage keys (it could be necessary for certain applications that rely on secure access),
# while also enabling the creation of device files (this can be necessary for applications that require direct access to hardware or specific device files.).
features: nesting=1,keyctl=1,mknod=1
hostname: portainer
memory: 4096
nameserver: 1.1.1.1 8.8.8.8
# Verify the LXC’s Network Mode
# If the Container Firewall is enabled (indicated by firewall=1), it may restrict network access for the container.
# If you encounter connectivity issues, consider disabling it by removing the firewall=1 setting in the container configuration.
# type=veth specifies that the network interface is a virtual Ethernet (veth) interface.
# Veth pairs are used in container networking to connect the container’s network namespace to the host’s network namespace.
net0: name=eth0,bridge=vmbr0,gw=192.168.1.1,hwaddr=BC:24:11:39:28:4C,ip=192.168.1.40/24,type=veth
onboot: 1
ostype: ubuntu
rootfs: mypool:subvol-110-disk-0,size=80G
startup: 1
swap: 512
# This line sets the AppArmor profile for the container to "unconfined."
# AppArmor is a Linux security module that restricts the capabilities of programs.
# By using the "unconfined" profile, the container is given broader permissions and is not restricted by AppArmor's security policies.
lxc.apparmor.profile: unconfined
# Restart the container
pct restart 110
# Verify from the host
pct exec 110 -- curl -k -I https://192.168.1.40:9443
This will allow traffic to reach your container’s Portainer service.
ssh-copy-id root@192.168.1.40
. Now try logging into the machine, with: ssh root@192.168.1.40
ssh root@myportainer
, create an alias myportainer = "ssh root@myportainer";
.