Generate Dynamic Opengraph Images using Gauguin
Generate dynamic open graph images with just HTML and CSS in the fastest way possible using Gauguin!

Have you tried to share any Hackdoor article on Facebook, Twitter, Reddit etc?

If you haven't, you should try right now with this article! ๐Ÿ˜›
I'm telling that for a reason: you will notice that this article opengraph image will look like this:

Generate Dynamic Opengraph Images using Gauguin

If you are an Hackdoor author, you may also have noticed that if you change your article's title or image, the opengraph image will immediately reflect those changes.

Well, that's because at Hackdoor, we're using Gauguin!

Generate Dynamic Opengraph Images using Gauguin

Gauguin is an opensource Golang server that lets you to generate dynamic opengraph images in the fastest way possible, and no Golang knowledge is required to get started!

#Getting Started with Gauguin

To get started with the current version of Gauguin (v0.0.X) you just need to have Docker (or both Go and Google Chrome) installed on your machine.

After installing Docker, let's create a new Gauguin project:

mkdir my-gauguin-project

at this point, enter into your project folder and create a new file called gauguin.yaml

touch gauguin.yaml

this will be the configuration file for our Gauguin project.

#Configuration

Pretending that we have a blog, while configuring Gauguin we want to specify the following the following data:

  1. The image variants we want to create (article image, author image and so on)
  2. Dynamic data: for the article image, for example, we want the following dynamic data: author name, article title, article cover image url.
  3. The size of the opengraph image (For Facebook is 1200x630px)
  4. The template we want to use for this opengraph image

We'll end up with a gauguin.yaml file that looks like this:

version: 0.0.1
routes:
  - path: /article/opengraph
    params:
      - title
      - author
      - imageurl
    size: '1200x630'
    template: ./templates/article/opengraph.tmpl
  - path: /user/opengraph
    params:
      - title
      - username
      - imageurl
    size: '1200x630'
    template: ./templates/user/opengraph.tmpl

#Templating

Now we want to define our templates! Let's create a new directory called templates and two subfolders, one for the article template and one for the user template.

For writing our templates we'll be using the standard Golang template engine, so that we can use variables (which we have already defined in our configuration file) and even some custom logic.

Let's start with the single article opengraph image (templates/article/opengraph.tmpl):

<!DOCTYPE html>
<html>
  <head>
    <style>
      body {
        margin: 0;
        font-family: Arial;
        color: #fff;
      }
      .article-template {
        display: flex;
        justify-content: center;
        align-items: center;
        flex-direction: column;
        width: 1200px;
        height: 630px;
        background: #001f1c;
      }
      h1 {
        margin: 0;
        font-size: 32px;
      }
      img {
        width: 200px;
        height: 200px;
        object-fit: cover;
        border-radius: 15px;
        margin-bottom: 25px;
      }
    </style>
  </head>
  <body>
    <div class="article-template">
      <img src="{{.imgUrl}}" />
      <h1>{{.title}}</h1>
      <p>Written by <b>{{.author}}</b></p>
    </div>
  </body>
</html>

as you can see, it is basically a standard HTML file with some variables. In Golang templates, variables are defined like this: {{.myVariable}}.

Great, we have our first template!

#Rendering the image

Now that we have defined our first template, we're ready to see the results!

As you may remember, in our gauguin.yaml file we defined some routes.
More precisely, for the articles' opengraph images, we've defined the following route:

/article/opengraph

Before we can actually test it, we need to start up our server.

Create a new docker-compose.yaml file, and fill it as follows:

version: '3.7'

services:

  alpine_chrome:
    image: zenika/alpine-chrome:latest
    container_name: gauguin-chrome-alpine
    command: [chromium-browser, "--headless", "--disable-gpu", "--no-sandbox", "--disable-dev-shm-usage", "--remote-debugging-address=0.0.0.0", "--remote-debugging-port=9222"]
    ports:
      - 9222:9222
    restart: unless-stopped
    networks:
      - gauguin_net

  gin_gonic:
    container_name: gauguin-gin-gonic
    image: docker.pkg.github.com/micheleriva/gauguin/server:v0.0.4
    links:
      - alpine_chrome
    depends_on:
      - alpine_chrome
    env_file:
      - .env
    ports:
      - 5491:5491
    expose:
      - '5491'
    volumes:
      - ./templates:/app/templates
      - ./gauguin.yaml:/app/gauguin.yaml
    restart: unless-stopped
    networks:
      - gauguin_net

networks:
  gauguin_net:
    driver: bridge

As you can see, under the gin_gonic service configuration, we're specifying that we want to use the v0.0.4 version of Gauguin, which is the most recent version at time of writing.

You can choose another version here: https://github.com/micheleriva/gauguin/releases.

Great! Now we can start up Docker by running:

$ docker-compose up -d --build

After the server started, we can finally visit our route by at the following address:

http://localhost:5491/articles/test

and that's what we see:

Generate Dynamic Opengraph Images using Gauguin

Don't get scared! We forgot to pass our variables!
In order to render the dynamic content, we need to pass our variables via query string.

We've previously defined thee variables:

  1. Title (title)
  2. Author (author)
  3. Image URL (imageUrl)

We want our og:image to dispay the following dynamic data:

  1. Title: "A Post About my Garden"
  2. Author: Bojack Horseman

and we want to render the following image (taken from Unsplash):

Generate Dynamic Opengraph Images using Gauguin

The full image url is https://images.unsplash.com/photo-1525498128493-380d1990a112?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=200&h=200&q=80&dev=true, but remember, we can't pass an URL via query string! We first need to encode it as an URI component.

We can do it by using JavaScript:

encodeURIComponent("https://images.unsplash.com/photo-1525498128493-380d1990a112?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=200&h=200&q=80&dev=true")

or using an online tool such as http://pressbin.com/tools/urlencode_urldecode/index.html.

In both ways, we want our image URL to look like this:

https%3A%2F%2Fimages.unsplash.com%2Fphoto-1525498128493-380d1990a112%3Fixlib%3Drb-1.2.1%26ixid%3DeyJhcHBfaWQiOjEyMDd9%26auto%3Dformat%26fit%3Dcrop%26w%3D200%26h%3D200%26q%3D80%26dev%3Dtrue

We're now ready to retry! Our full URL will be:

http://localhost:5491/articles/test?author=Bojack%20Horseman&title=A%20Post%20About%20my%20Garden&imgUrl=https%3A%2F%2Fimages.unsplash.com%2Fphoto-1525498128493-380d1990a112%3Fixlib%3Drb-1.2.1%26ixid%3DeyJhcHBfaWQiOjEyMDd9%26auto%3Dformat%26fit%3Dcrop%26w%3D300%26q%3D80&dev=true

And that is the result!

Generate Dynamic Opengraph Images using Gauguin

If you want to inspect the template using chrome, just use the exact same URL as above and append &dev=true. With that parameter specified, Gauguin will just render the HTML instead of the image!

#Deployment

Deploying Gauguin is incredibly easy. Thanks to its architecture, it can work well even on a 5$ DigitalOcean Droplet.

If you want to try, you can use the following link for getting $100 of free DigitalOcean services for the next three months: https://m.do.co/c/3876c597ccc7

After you've created your Droplet (or your EC2/VPS), you just need to create two files:

docker-compose.yaml

version: '3.7'

services:

  alpine_chrome:
    image: zenika/alpine-chrome:latest
    container_name: gauguin-chrome-alpine
    command: [chromium-browser, "--headless", "--disable-gpu", "--no-sandbox", "--disable-dev-shm-usage", "--remote-debugging-address=0.0.0.0", "--remote-debugging-port=9222"]
    ports:
      - 9222:9222
    restart: unless-stopped
    networks:
      - caddynet

  gin_gonic:
    container_name: gauguin-gin-gonic
    image: docker.pkg.github.com/micheleriva/gauguin/server:v0.0.4
    links:
      - alpine_chrome
      - caddy
    depends_on:
      - alpine_chrome
    env_file:
      - .env
    ports:
      - 5491:5491
    expose:
      - '5491'
    volumes:
      - ./templates:/app/templates
      - ./gauguin.yaml:/app/gauguin.yaml
    restart: unless-stopped
    networks:
      - caddynet

  caddy:
    image: abiosoft/caddy
    container_name: gauguin-caddy
    cap_add:
      - CAP_NET_BIND_SERVICE
    ports:
      - 80:80
      - 443:443
    expose:
      - '443'
      - '80'
    volumes:
      - ./Caddyfile:/etc/Caddyfile
    command: -conf /etc/Caddyfile
    restart: always
    networks:
      - caddynet

networks:
  caddynet:
    driver: bridge

We want to use Caddy as a reverse proxy for exposing Gauguin to the outside world, so we also need to create a Caddyfile.

Caddyfile:

example.com:80, example.com:443 {
  gzip
  tls youremail@example.com
  tls self_signed

  proxy / gauguin-gin-gonic:5491 {
    transparent
  }
}

Make sure to change example.com with your own domain! ๐Ÿ˜„

Now move your local templates directory and your local gauguin.yaml file in the same folder of both docker-compose.yaml and Caddyfile and run:

docker-compose up -d

and you're done! Gauguin is up and running!