Arama Yap Mesaj Submit
Request a Callback
+90
X
X

Select Your Currency

Turkish Lira $ US Dollar Euro
X
X

Select Your Currency

Turkish Lira $ US Dollar Euro

Contact Us

Location Halkali merkez neighborhood fatih st ozgur apt no 46 , Kucukcekmece , Istanbul , 34303 , TR
Ubuntu 24.04 Docker and Portainer Setup: Nginx, SSL and Secure 9443
Docker, Portainer, Nginx, Let's Encrypt and Ubuntu 24.04

Docker, Docker Compose and Portainer on Ubuntu 24.04: Complete Nginx + SSL Illustrated Guide

This guide documents a real Ubuntu 24.04.4 LTS VPS installation: Docker Engine from the official repository, Compose and Buildx validation, a real hello-world container test, Portainer CE LTS with persistent storage, Nginx reverse proxy and Let's Encrypt for portainer.ekasunucu.com, a real Certbot snap timeout, and the final security step that binds Portainer 9443 only to 127.0.0.1.

Ubuntu 24.04DockerDocker EngineDocker ComposePortainerPortainer CENginxLet's EncryptCertbotReverse ProxyDocker VPSLinux VPSSelf HostedEKA Server
Docker / Portainer / Ubuntu 24.04
Ubuntu 24.04.4 LTS
   ↓ Docker Engine + Compose
Docker socket /var/run/docker.sock
   ↓
Portainer CE LTS :9443
   ↓ localhost only
Nginx :443 + Let's Encrypt
   ↓
portainer.ekasunucu.com
Docker29.7.2Portainer2.39.5 LTS
15real WebP screenshots
3TR · EN · DE content
443public HTTPS
127.0.0.1Portainer loopback
01Official Docker repo + Engine + Compose
02hello-world and docker info validation
03Portainer CE LTS + persistent portainer_data
04Nginx + SSL + localhost-only 9443
00
Table of contents

Ubuntu 24.04 Docker + Portainer installation steps

  1. 01How did we build the Docker + Portainer architecture on Ubuntu 24.04?
  2. 02Check the operating system, resources and potentially conflicting Docker packages
  3. 03Add Docker's official Ubuntu Noble repository with a keyring
  4. 04Install Docker Engine, Docker Compose and Buildx
  5. 05Interpret exit code 141 caused by head under set -o pipefail correctly
  6. 06Verify the Docker daemon chain with hello-world and docker info
  7. 07Create portainer_data and start the Portainer CE LTS container
  8. 08Create the first administrator account and keep the setup token secret
  9. 09Install Nginx, verify DNS and place Portainer behind the domain
  10. 10If the Certbot snap download times out, isolate the network layer instead of rebuilding Nginx
  11. 11Do not keep APT and snap Certbot installations active together
  12. 12Issue a certificate for portainer.ekasunucu.com and deploy it to Nginx
  13. 13Close public access to Portainer 9443 and bind it only to 127.0.0.1
  14. 14Test the Certbot timer and renew --dry-run
  15. 15Log in through the HTTPS domain and open the local environment
  16. 16Verify Docker, Nginx, Portainer, TLS and listening ports separately
01
Real test environment

How did we build the Docker + Portainer architecture on Ubuntu 24.04?

This guide records a real installation on an Ubuntu 24.04.4 LTS VPS. Docker Engine 29.7.2, Docker Compose v5.4.0 and Portainer Community Edition 2.39.5 LTS were used in the test environment; these version numbers describe the tested system rather than a permanent requirement.

The goal was not merely to open Portainer. Docker was installed from the official repository, Portainer data was persisted in a named volume, Nginx was placed in front as a reverse proxy, Let's Encrypt TLS was enabled, and the final Portainer 9443 mapping was restricted to 127.0.0.1 so the raw management port was no longer exposed to the Internet.

Command 1
Internet :443
     ↓
Nginx + Let's Encrypt
     ↓
https://127.0.0.1:9443
     ↓
Portainer CE LTS
     ↓
/var/run/docker.sock → Docker Engine
02
Pre-checks

Check the operating system, resources and potentially conflicting Docker packages

Before installation, record the Ubuntu release, kernel, architecture, memory and disk capacity. The test VPS ran Ubuntu 24.04.4 LTS Noble, kernel 6.8.0-137-generic and x86-64 architecture.

If you plan to use Docker's official packages, first inspect existing docker.io, docker-compose, containerd, runc or podman-docker packages. On production servers, do not remove packages blindly without checking running workloads.

Command 1
cat /etc/os-release | grep -E 'PRETTY_NAME|VERSION_ID|VERSION_CODENAME'
uname -r
dpkg --print-architecture
Command 2
free -h
df -h /
Command 3
dpkg --get-selections | grep -E 'docker|containerd|runc|podman' || true
03
Official Docker APT repository

Add Docker's official Ubuntu Noble repository with a keyring

We installed Docker Engine from Docker's official Ubuntu repository. ca-certificates and curl were prepared, the signing key was stored at /etc/apt/keyrings/docker.asc and a deb822 docker.sources file was created.

A docker-ce candidate from the Noble stable repository in apt-cache policy confirms that the repository layer is working. The candidate in our test was Docker CE 29.7.2.

Command 1
apt-get update
apt-get install -y ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
Command 2
cat >/etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
apt-get update
apt-cache policy docker-ce
04
Engine and Compose

Install Docker Engine, Docker Compose and Buildx

After repository validation, we installed docker-ce, docker-ce-cli, containerd.io, docker-buildx-plugin and docker-compose-plugin. The test session downloaded 102 MB; the amount varies by release and system state.

We enabled and started Docker through systemd, then checked Docker, Compose and Buildx versions individually so that both the engine and required CLI plugins were verified.

Command 1
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Command 2
systemctl enable --now docker
systemctl is-active docker
Command 3
docker --version
docker compose version
docker buildx version
05
Real troubleshooting record

Interpret exit code 141 caused by head under set -o pipefail correctly

Our first automation script stopped with exit code 141 on a head -n 20 line even though the Docker repository was healthy. Because head closed the pipe early, the upstream command received SIGPIPE and set -o pipefail treated it as a script failure.

For display-only pipelines you can use sed -n, or append || true only when you have confirmed the failure is harmless. Never hide failures from installation, validation or state-changing commands this way.

Command 1
apt-cache policy docker-ce | sed -n '1,20p'
Command 2
apt-cache policy docker-ce | head -n 20 || true
06
End-to-end Docker test

Verify the Docker daemon chain with hello-world and docker info

docker --version proves only that the client exists. We therefore pulled and ran hello-world. The 'Hello from Docker!' result confirmed the client, daemon, image pull and container execution path together.

docker info was then used to record the storage driver, cgroup mode, security options, Docker Root Dir and server resources. The test showed overlayfs, cgroup v2, AppArmor and seccomp.

Command 1
docker run --rm hello-world
Command 2
docker info
Command 3
systemctl --failed
07
Portainer CE LTS

Create portainer_data and start the Portainer CE LTS container

We created a Docker named volume called portainer_data so Portainer configuration survives container replacement. The portainer/portainer-ce:lts image was pulled and the local Docker socket was mounted into the container.

During the real first validation, port 9443 was briefly published on the host to confirm the HTTPS endpoint. This was not the final security posture; after Nginx and the domain were ready, Portainer was recreated with 127.0.0.1:9443 only.

Command 1
docker volume create portainer_data
docker pull portainer/portainer-ce:lts
Command 2
docker run -d --name portainer --restart=always -p 9443:9443 -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:lts
Command 3
docker ps --filter name='^/portainer$'
curl -k -sS -o /dev/null -w '%{http_code}\n' https://127.0.0.1:9443/
08
First administrator account

Create the first administrator account and keep the setup token secret

A fresh Portainer database shows the administrator-account setup page. In our test, Portainer logs also generated a temporary setup token for initial administration. Treat it like a password or API secret and never publish it in screenshots.

After the administrator is created, the Environment Wizard can detect the local Docker environment. If an initial setup session expires after being left idle, restart the Portainer container and continue.

Command 1
docker logs portainer --tail 100
Command 2
docker restart portainer
09
Domain and reverse proxy

Install Nginx, verify DNS and place Portainer behind the domain

We installed Nginx and dnsutils to serve Portainer through portainer.ekasunucu.com instead of a raw IP and port. Reverse-proxy configuration began only after Cloudflare DNS and Google DNS resolved the hostname to the expected origin.

Nginx proxies requests to Portainer's HTTPS service on 127.0.0.1:9443. Portainer uses a self-signed certificate for this local upstream, so proxy_ssl_verify off is limited to that localhost hop while public TLS is terminated by Nginx with Let's Encrypt.

Command 1
apt-get update
apt-get install -y nginx dnsutils
systemctl enable --now nginx
Command 2
dig +short A portainer.ekasunucu.com @1.1.1.1
dig +short A portainer.ekasunucu.com @8.8.8.8
Command 3
cat >/etc/nginx/conf.d/portainer-websocket.conf <<'EOF'
map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}
EOF
Command 4
cat >/etc/nginx/sites-available/portainer.ekasunucu.com <<'EOF'
server {
    listen 80;
    listen [::]:80;
    server_name portainer.ekasunucu.com;

    location / {
        proxy_pass https://127.0.0.1:9443;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_ssl_verify off;
    }
}
EOF
ln -sfn /etc/nginx/sites-available/portainer.ekasunucu.com /etc/nginx/sites-enabled/portainer.ekasunucu.com
nginx -t
systemctl reload nginx
Command 5
curl -sS -o /dev/null -w '%{http_code}\n' -H 'Host: portainer.ekasunucu.com' http://127.0.0.1/
10
Certbot troubleshooting

If the Certbot snap download times out, isolate the network layer instead of rebuilding Nginx

The first snap install --classic certbot attempt timed out while downloading core24 from Canonical's CDN. The error was in the snap download path, not in the working Nginx-to-Portainer reverse proxy.

We checked snap changes and snapd status. A later retry installed Certbot 5.7.0 successfully. For temporary CDN/network errors, retry and diagnose connectivity before changing unrelated application configuration.

Command 1
snap changes
systemctl is-active snapd
Command 2
snap install --classic certbot
Command 3
snap list certbot
/snap/bin/certbot --version
11
One Certbot source

Do not keep APT and snap Certbot installations active together

During troubleshooting we briefly tested Ubuntu's APT Certbot packages. In the final setup, APT certbot, python3-certbot and python3-certbot-nginx were removed and the server was standardized on the snap build of Certbot 5.7.0.

Using one binary and one renewal mechanism makes troubleshooting simpler. On an existing production server, inspect certificate and renewal configuration before removing packages.

Command 1
apt-get remove -y certbot python3-certbot python3-certbot-nginx python3-certbot || true
Command 2
snap install --classic certbot
ln -sfn /snap/bin/certbot /usr/local/bin/certbot
certbot --version
12
Let's Encrypt TLS

Issue a certificate for portainer.ekasunucu.com and deploy it to Nginx

Once the HTTP reverse proxy returned 200, we issued the Let's Encrypt certificate. Certbot stored it under /etc/letsencrypt/live/portainer.ekasunucu.com and updated Nginx for HTTPS.

After issuance we verified nginx -t, the HTTPS response and certificate subject, issuer and validity dates with openssl instead of relying only on the browser.

Command 1
certbot --nginx -d portainer.ekasunucu.com -m [email protected] --agree-tos --no-eff-email --redirect
Command 2
nginx -t
systemctl is-active nginx
Command 3
curl -I https://portainer.ekasunucu.com/
Command 4
openssl s_client -connect portainer.ekasunucu.com:443 -servername portainer.ekasunucu.com </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates
13
Tighten the management port

Close public access to Portainer 9443 and bind it only to 127.0.0.1

After the domain and Nginx TLS path worked, we recreated the Portainer container with the same portainer_data volume and changed the host mapping to 127.0.0.1:9443:9443. The management port therefore stopped listening on the server's public interfaces.

The named volume preserves the Portainer database across container replacement. In the final socket listing, 9443 should be loopback-only while public 80/443 belong to Nginx.

Command 1
docker stop portainer
docker rm portainer
Command 2
docker run -d --name portainer --restart=always -p 127.0.0.1:9443:9443 -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:lts
Command 3
docker ps --filter name='^/portainer$'
ss -lntp | grep -E ':(80|443|9443)\b'
Command 4
curl -k -sS -o /dev/null -w '%{http_code}\n' https://127.0.0.1:9443/
curl -sS -o /dev/null -w '%{http_code}\n' https://portainer.ekasunucu.com/
14
Automatic renewal

Test the Certbot timer and renew --dry-run

A certificate that works today still needs a verified renewal path. We checked the snap.certbot.renew timer and executed certbot renew --dry-run.

The simulated renewal succeeded. Combined with nginx -t, this is an important production check before leaving the TLS setup unattended.

Command 1
systemctl list-timers --all | grep -i certbot || true
Command 2
certbot renew --dry-run
Command 3
nginx -t
15
Web interface

Log in through the HTTPS domain and open the local environment

The final Portainer login page was reached only through https://portainer.ekasunucu.com. After login, the Environment Wizard detected the local Docker socket and offered the local environment through the Get Started flow.

The Portainer home screen showed the local environment as Up along with container, volume, image, CPU and RAM information, confirming that the UI could actually manage Docker Engine.

Command 1
https://portainer.ekasunucu.com
16
Pre-production checks

Verify Docker, Nginx, Portainer, TLS and listening ports separately

At the end, verify Docker and Nginx services, the Portainer container, named volume, Docker disk use, HTTPS status code and failed systemd units separately. Our test ended with HTTP 200 and zero failed systemd units.

Repeat these checks after a reboot. Docker restart policy, Nginx auto-start, Portainer state and domain TLS should not depend on an interactive SSH session.

Command 1
systemctl is-active docker nginx
docker ps
docker volume ls
Command 2
docker system df
Command 3
curl -sS -o /dev/null -w 'HTTPS %{http_code}\n' https://portainer.ekasunucu.com/
Command 4
ss -lntp | grep -E ':(80|443|9443)\b'
systemctl --failed
Production checklist

Docker + Portainer production security checklist

Do not leave Portainer 9443 exposed on the public IP; bind it to loopback when using a reverse proxy.
Never expose an unauthenticated Docker daemon TCP socket to the Internet.
Use a strong unique Portainer administrator password and protect setup tokens and backups.
Back up the portainer_data volume and critical container data regularly.
Review how Docker-published ports interact with host firewall rules.
Publish only required hostnames in Nginx and always run nginx -t before reloading.
Verify the Certbot renewal timer and renew --dry-run result.
Record versions, volumes and rollback options before Docker or Portainer upgrades.
Re-test Docker, Nginx, Portainer and HTTPS after reboot.
R
Official sources

Official Docker, Portainer, Nginx and Certbot documentation

+
EKA Sunucu

Related EKA Server Linux and self-hosted guides

?
FAQ

Frequently asked questions about Docker and Portainer on Ubuntu 24.04

Does Docker run on Ubuntu 24.04 LTS?

Yes. This guide records Docker Engine 29.7.2 running on Ubuntu 24.04.4 LTS Noble. For new deployments, use the currently supported versions from Docker's official Ubuntu documentation.

Why use Docker's official repository?

It provides Docker CE, Compose and Buildx through Docker's own Ubuntu package channel and keeps the related plugin packages aligned.

Why is docker run hello-world important?

docker --version checks only the client. hello-world verifies daemon connectivity, image pull, container creation and output end to end.

Which port does Portainer use?

Portainer provides HTTPS on 9443. In the final configuration here, the host listens only on 127.0.0.1:9443 and public access is served by Nginx on 443.

Is Portainer's self-signed certificate a problem?

It is used only on the localhost upstream hop in this design. Public TLS is terminated by Nginx with a Let's Encrypt certificate.

Will Portainer settings disappear when the container is recreated?

Not if the portainer_data named volume is preserved. Do not delete the volume when replacing the container.

What is the Portainer setup token?

It is a sensitive temporary value protecting initial administration on a fresh installation. Treat it like a password and do not publish it.

What if the Certbot snap installation times out?

Check snap changes, snapd and outbound HTTPS. In our test a Canonical CDN download timed out and a later retry succeeded.

Should APT Certbot and snap Certbot be used together?

Not in the final setup documented here. We removed the APT Certbot packages and standardized on the snap build.

Why close public port 9443?

Nginx already provides secure domain access on 443, so there is no need to leave the raw Portainer management port exposed on the public IP.

How can I test automatic certificate renewal?

Run certbot renew --dry-run to simulate the renewal path without replacing the active certificate. Our test completed successfully.

How is the local Portainer environment added?

With /var/run/docker.sock mounted into Portainer, Environment Wizard can detect the local Docker host and offer it through Get Started.

Which ports should be listening after installation?

In this architecture Nginx listens publicly on 80/443 while Portainer 9443 is loopback-only. Manage SSH and other services according to your own security policy.

Can I run this on an EKA Server VPS?

The same architecture can be used on a suitable Ubuntu VPS with root/SSH access, enough RAM and disk, and an appropriate security policy. Resource needs depend on the containers you run.

EKA YAZILIM VE BİLİŞİM SİSTEMLERİ

Need an Ubuntu VPS for Docker and Portainer?

Run Docker Engine, Portainer, Nginx, Ollama, Open WebUI, n8n and other self-hosted services on your own infrastructure with EKA Server Linux VPS plans.

Updated: 10.08.2026
View Linux VPS PlansLinux & VPS Guides
Top