blog/content/posts/how-to-portainer.md

6.9 KiB

+++ date = '2025-04-14' draft = true title = "How to Host Docker Containers Easily in The Cloud" tags = ["howto", "tutorial", "web"] categories = ["technical"] +++

In this post, we will be going over how to set up a portainer managed docker environment, and how to use it. This is ideal if you want to host a personal website, a blog, a personal github or whatever your development heart desire. If you choose to follow along, by the end of it, you will have an environment where you can just add or remove docker based services at a whim using a nice web-based interface.

I assume that you already know about docker and docker compose yaml syntax. If you don't, may I recommend the wonderful official docker tutorial - once you're done with that come back here. Or just read on and roll with the punches.

Oh yea, you should also have good knowledge and experience working on GNU/Linux systems, as you'll be doing a lot of management and interaction with the terminal both during the setup process and during maintenance.

Server

The very first thing to get is a server. This can either be the machine you're currently using if you don't want to mess around on the public internet, or it could be an actual desktop you have set up with a public IP. Or it could be a VPS (Virtual Private Server) - which is just a fancy word for a "cloud computer" that someone else hosts and powers, and you just get an SSH connection to it. Any VPS provider will work, but digital ocean is very affordable and easy to use. As long as you get a VPS and avoid a webhotel, you should be fine (side note: webhotels are a scam and you shouldn't ever use them - especially not if you're tech-savvy enough to read this blog).

Once you have your server, install docker on it. Preferably the latest version.

Traefik and Portainer

The very first thing to get done is set up portainer and traefik. This is done by creating a new docker-compose.yml file on your server. Just to keep things tidy, you should make a directory for all you are going to do here.

# Make the config directory in your $HOME dir - this is where 
# we'll be working throughout the tutorial. If not specified
# otherwise, you should only be editing files inside this directory.
mkdir -p ~/config
mkdir -p ~/config/traefik-data
mkdir -p ~/config/portainer-data
cd ~/config

# Create an empty yaml file
touch docker-compose.yml

It might be a good idea to initialize the control directory as a (local) git project. That way you will always have a history of what you have been done, and what you did when you (inevitably) break things. This I will leave up to you though (probably gitignore the portainer-data directory).

Inside the new docker-compose.yml file, you should put the following content (open the file using your favorite terminal text editor).

# docker-compose.yml
services:
  traefik:
    image: traefik:latest
    container_name: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    networks:
      - proxy
    ports:
      - 80:80
      - 443:443
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik-data/traefik.yml:/traefik.yml:ro
      - ./traefik-data/acme.json:/acme.json
      - ./traefik-data/configurations:/configurations
    labels:
      - traefik.enable=true
      - traefik.docker.network=proxy
      - traefik.http.routers.traefik-secure.entrypoints=websecure
      - traefik.http.routers.traefik-secure.rule=Host(`traefik.example.com`)
      - traefik.http.routers.traefik-secure.service=traefik
      - traefik.http.routers.traefik-secure.middlewares=user-auth@file
      - traefik.http.routers.traefik-secure.service=api@internal

  portainer:
    image: portainer/portainer-ce:alpine
    container_name: portainer
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    networks:
      - proxy
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./portainer-data:/data
    labels:
      - traefik.enable=true
      - traefik.docker.network=proxy
      - traefik.http.routers.portainer-secure.entrypoints=websecure
      - traefik.http.routers.portainer-secure.rule=Host(`portainer.example.com`)
      - traefik.http.routers.portainer-secure.service=portainer
      - traefik.http.services.portainer.loadbalancer.server.port=9000

networks:
  proxy:
    external: true

Whew! That's a lot. Let's break it down. We define two services traefik and portainer. Starting with the things that are common to both of them, we set the initial niceties, such as the container_name, restart policy, security options and set their shared network to be the externally defined proxy network. Both services need (read-only) access to the system time for various reasons, so we volume mount /etc/localtime to their respective internal /etc/localtime. They also both need access to the system docker socket, so we also volume mount that in (again, read-only). Then we map the various configuration files in (we will soon make these).

If you haven't used traefik before, you might be scratching your head on the labels that we set on each of the services. This is how you configure services to integrate into traefik, enabling you to route your various containers to various subdomains, integrate middlewares such as forcing HTTPS and setting load-balancer settings etc.

Let's add the configuration files, shall we?

Keycloak

TODOs

  • 2FA the control dashboards through keycloak
  • geoblocking the control dashboards
  • start the article with a demo of what we'll be making
  • MAYBE:
    • portainer introduction (maybe)
    • traefik introduction (maybe)
    • add a "skip if you already know portainer and traefik"
services:
  postgresql:
    image: postgres:16
    environment:
      - POSTGRES_USER=keycloak
      _ POSTGRES_DB=keycloak
      - POSTGRES_PASSWORD=secret
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - keycloak


  keycloak:
    image: quay.io/keycloa/keycloak:22
    restart: always
    command: start
    depends_on:
      - postgresql
    environment:
      # traefik handles ssl
      - KC_PROXY_ADDRESS_FORWARDING=true
      - KC_HOSTNAME_STRUCT=false
      - KC_HOSTNAME=keycloak.gtz.dk
      - KC_PROXY=edge
      - KC_HTTP_ENABLED=true
      # connect to the postgres thing
      - DB=keycloak
      - DB_URL='jdbc:postgresql://postgres:5432/postgresql?ssl=allow'
      - DB_USERNAME=keycloak
      - DB_PASSWORD=secret
      - KEYCLOAK_ADMIN=admin
      - KEYCLOAK_ADMIN_PASSWORD=admin
    networks:
      - proxy
      - keycloa
    labels:
      - "traefik.enable=true"
      - port=8080

networks:
  proxy:
    external: true
  keycloak:

{{< centered image="/6616144.png" >}}