186 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			186 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| +++
 | |
| 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](https://www.portainer.io/) managed docker environment,
 | |
| and how to use it. This is ideal if you want to host a personal website, a [blog](/posts/how-to-blog), a personal
 | |
| [github](git.gtz.dk) 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.
 | |
| 
 | |
| 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](https://docs.docker.com/get-started/workshop/) - 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](https://www.digitalocean.com/) 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](https://docs.docker.com/engine/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.
 | |
| 
 | |
| ```sh
 | |
| # 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).
 | |
| 
 | |
| ```yaml
 | |
| # 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
 | |
|  - [x] 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"
 | |
| 
 | |
| 
 | |
| ```yaml
 | |
| 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" >}}
 |