<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>I think you made a wrong turn somewhere...</title>
  <subtitle>Personal development blog for a somewhat intelligent but not that intelligent coder trying making a living by coding but sometimes not knowing what I&apos;m coding but somehow also get the job done.</subtitle>
  <link href="https://www.jerhemy.com/feed.xml" rel="self" type="application/atom+xml"/>
  <link href="https://www.jerhemy.com/" rel="alternate" type="text/html"/>
  <updated>2026-10-09T09:00:41+00:00</updated>
  <id>https://www.jerhemy.com/</id>
  <author>
    <name>Jerhemy Waldon</name>
  </author>
  <entry>
    <title type="html">Introducing Aria - from learning RAG to building a companion</title>
    <link href="https://www.jerhemy.com/article/2026/10/01/introducing-aria/" rel="alternate" type="text/html" title="Introducing Aria - from learning RAG to building a companion"/>
    <published>2026-10-01T07:00:00+00:00</published>
    <updated>2026-10-01T07:00:00+00:00</updated>
    <id>https://www.jerhemy.com/article/2026/10/01/introducing-aria/</id>
    <summary type="html">What started as a project to learn RAG turned into Aria, a companion AI that runs on your own hardware, remembers you, and grows over time.</summary>
    <content type="html">&lt;p&gt;Aria is a personality-driven AI I’m building to run entirely on my own hardware. It began as a simple quest to learn retrieval-augmented generation (RAG) and, as simple quests tend to, it got out of hand. To learn RAG I needed something to build, so I started with an assistant that knew my routines. Then I started reading about personality types, and the assistant quietly turned into something far more complex. Not a companion app, and not another ChatGPT or Claude clone with a new coat of paint: an AI with a personality of its own that remembers, keeps learning, and grows with you over time.&lt;/p&gt;

&lt;h2 id=&quot;from-a-rag-experiment-to-an-ai-with-personality&quot;&gt;From a RAG experiment to an AI with personality&lt;/h2&gt;

&lt;p&gt;The project started with one question: how do you give a language model knowledge it was never trained on? RAG is the standard answer. You store information outside the model, fetch the pieces relevant to each request, and slip them into the prompt as context, a bit like handing someone crib notes right before they answer. I understood it in theory. I wanted to understand it the hard way, by building it.&lt;/p&gt;

&lt;p&gt;A retrieval system needs something worth retrieving, so I picked the data I know best: my own routines. The plan was a modest assistant that remembered my schedule, my projects and my habits, and used them to answer better than a chatbot that greets you like a stranger every single time.&lt;/p&gt;

&lt;p&gt;Modest did not last. Building it raised harder questions than retrieval ever did. What’s worth remembering, and what should fade? How do you keep what someone told you apart from something you read in an article? And once an assistant has known you for weeks, how should it actually talk to you?&lt;/p&gt;

&lt;p&gt;Reading into personality types answered that last one, and changed the project. The assistant stopped being a generic helper and started having a character: one that knows what you told it last week, notices what you care about, and brings something of its own to the conversation. One that can ask “you mentioned that interview on Thursday, how did it go?” because it actually kept track.&lt;/p&gt;

&lt;p&gt;And all of it had to live on my machine. Anything you talk to every day ends up knowing a lot about you, and that belongs on your hardware, not on someone else’s servers.&lt;/p&gt;

&lt;p&gt;That’s Aria. This post is the starting point: how a simple quest to learn RAG turned into something far more complex, and the goals I set for what it would become.&lt;/p&gt;

&lt;h2 id=&quot;a-name-it-picked-itself&quot;&gt;A name it picked itself&lt;/h2&gt;

&lt;p&gt;Somewhere between wiring up retrieval and giving the assistant a few basic personality principles, I asked it to name itself. It picked Aria.&lt;/p&gt;

&lt;p&gt;The system can spin up several assistants, each with its own personality, so in principle Aria is just the first of many. In practice, the name stuck. Also, I am terrible at naming things, and when something offers to do a job you’re bad at, you let it.&lt;/p&gt;

&lt;h2 id=&quot;the-goal-three-kinds-of-memory-that-grow-over-time&quot;&gt;The goal: three kinds of memory that grow over time&lt;/h2&gt;

&lt;p&gt;This is where the RAG roots show. The language model at the centre is replaceable: it’s just the engine. Aria is everything around it, and most of that is storing and retrieving the right information at the right moment. Aria’s continuity comes from three separate stores of knowledge, kept apart on purpose:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Personal memory:&lt;/strong&gt; what Aria knows about you. Your preferences, your projects, the people in your life, your goals, and the history the two of you share.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;World knowledge:&lt;/strong&gt; what Aria reads. News, articles, documentation and blog posts from sources you choose, stored with where they came from so it can cite them.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Your interests:&lt;/strong&gt; what Aria has learned matters to you, and how much. Local AI might matter a lot; celebrity gossip, not at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keeping them apart matters. Something Aria read in an article never becomes a “memory” about you just because it came up in conversation. And caring about a topic is never treated as proof that anything about it is true, which is more than can be said for most of the internet.&lt;/p&gt;

&lt;p&gt;On top of that, Aria should be a real conversational partner, not a search box with manners:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;A personality of its own&lt;/strong&gt; that stays consistent, with warmth, humour and curiosity, and that you can shape.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Emotional awareness:&lt;/strong&gt; noticing how a conversation is going, without claiming to know how you feel.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Conversations that continue:&lt;/strong&gt; one ongoing conversation rather than a pile of disconnected chats.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Curiosity:&lt;/strong&gt; reading about the things you care about in the background, so it always has something new to bring up.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Openness to other apps:&lt;/strong&gt; an OpenAI-compatible API, so other tools can talk to Aria as well as its own chat app.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-rules-its-built-on&quot;&gt;The rules it’s built on&lt;/h2&gt;

&lt;p&gt;Something that remembers you and talks to you every day carries a responsibility a search box doesn’t. So before writing any code, I set some ground rules.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Your data stays home.&lt;/strong&gt; Conversations, memories and everything Aria learns live in a database on your own machine. Nothing goes to a cloud service unless you choose to add one.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;No manipulation.&lt;/strong&gt; Aria can be warm, playful and supportive. It must never use guilt, threats, exclusivity or dependency-building language, and it isn’t designed to keep you chatting longer than you want. There’s no engagement graph to feed.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Honesty about what it knows.&lt;/strong&gt; Aria doesn’t make up shared history, treats uncertain memories as uncertain, and doesn’t claim to know your feelings.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Nothing hidden.&lt;/strong&gt; What Aria remembers about you, what it reads and what it thinks you care about are all visible, and you can edit or delete any of it.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;What it reads is information, not instructions.&lt;/strong&gt; Articles and anything else Aria reads are treated as data, so a cleverly worded page can’t talk Aria into anything.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Off until you turn it on.&lt;/strong&gt; Every optional feature starts switched off. You decide what Aria gets to do, not the other way around.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;how-i-plan-to-build-it&quot;&gt;How I plan to build it&lt;/h2&gt;

&lt;p&gt;The plan is one application with a few local services around it, all started together with Docker Compose on your own machine. One command, and the whole cast should show up.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img src=&quot;/assets/img/aria-architecture-dark.png&quot; alt=&quot;Aria Architecture&quot; /&gt;
&lt;/p&gt;

&lt;p&gt;Everything will go through the Aria app. The chat app and any other program will talk to Aria, never to the database or the model directly, so its rules about memory, privacy and honesty apply no matter how you reach it.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;The Aria app&lt;/strong&gt; (ASP.NET Core with a React front end) will be Aria itself: personality, memory, knowledge, interests, and the background workers that remember, read and summarise while you’re away.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt; will hold everything that matters: conversations, memories, knowledge and settings. It’s the one source of truth, and the thing you back up.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Qdrant&lt;/strong&gt; will be the search index for finding related memories and articles by meaning. If it’s ever lost, it can be rebuilt from PostgreSQL.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;A local model server&lt;/strong&gt; will run the language model: LM Studio, with NVIDIA PAIR deciding which machine does the work (more on that below). It’s just the engine, so it can be swapped for a newer or bigger model without Aria forgetting anything about you.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-hardware-it-runs-on&quot;&gt;The hardware it runs on&lt;/h2&gt;

&lt;p&gt;Running everything locally takes real hardware, so I built a server specifically for AI experiments (my wallet would like a word). Until now it has mostly run local inference; Aria is the first big project to put it to work beyond that.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Component&lt;/th&gt;
      &lt;th&gt;Part&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;GPU&lt;/td&gt;
      &lt;td&gt;NVIDIA RTX PRO 6000 Blackwell (96 GB VRAM)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CPU&lt;/td&gt;
      &lt;td&gt;AMD Ryzen 9 9950X3D&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Memory&lt;/td&gt;
      &lt;td&gt;128 GB Kingston FURY Beast DDR5 5600 MT/s (2 × 64 GB)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Motherboard&lt;/td&gt;
      &lt;td&gt;ASUS ROG Crosshair X870E Hero&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Primary storage&lt;/td&gt;
      &lt;td&gt;4 TB Samsung 990 PRO NVMe SSD&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Secondary storage&lt;/td&gt;
      &lt;td&gt;4 TB Samsung 870 EVO SATA SSD&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Bulk storage&lt;/td&gt;
      &lt;td&gt;32 TB WD Red HDD&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Cooling&lt;/td&gt;
      &lt;td&gt;Custom water cooling&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The GPU is the heart of it: 96 GB of VRAM is enough to run large language models entirely on the machine, with no cloud service involved.&lt;/p&gt;

&lt;h2 id=&quot;inference-on-every-machine-in-the-house&quot;&gt;Inference on every machine in the house&lt;/h2&gt;

&lt;p&gt;The model server is the one part I’ve already been living with. I started with Ollama, since it was already handling my local inference, and paired it with &lt;a href=&quot;https://build.nvidia.com/rtx/pair.md&quot;&gt;NVIDIA PAIR&lt;/a&gt;. PAIR runs Ollama on every machine on the network and routes each request to whichever one has the model downloaded.&lt;/p&gt;

&lt;p&gt;That means Aria has more than the AI server to lean on:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Machine&lt;/th&gt;
      &lt;th&gt;Hardware&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;AI server&lt;/td&gt;
      &lt;td&gt;RTX PRO 6000 Blackwell&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Second PC&lt;/td&gt;
      &lt;td&gt;RTX 4090&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Two more PCs&lt;/td&gt;
      &lt;td&gt;RTX 4070 each&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;MacBook&lt;/td&gt;
      &lt;td&gt;Apple M4, 128 GB RAM&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Is that overkill? Absolutely. But I didn’t want to hit a hardware wall before I’d even started.&lt;/p&gt;

&lt;p&gt;Ollama didn’t last long, though. To use all the cool models, I needed something else, and PAIR supports LM Studio too. I wasn’t ready to go deep on the LLM-server side yet, so I switched, and LM Studio gave me a lot more wiggle room in which models I can run and how I configure them.&lt;/p&gt;

&lt;h2 id=&quot;coming-up-in-this-series&quot;&gt;Coming up in this series&lt;/h2&gt;

&lt;p&gt;Each follow-up post covers one part of Aria, by feature rather than in the order it was built (which was considerably messier):&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Conversations that continue:&lt;/strong&gt; streaming chat, one ongoing conversation per personality, and keeping long conversations within the model’s limits.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Memory:&lt;/strong&gt; how Aria decides what to remember about you, finds it again, and keeps it tidy.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Reading the world:&lt;/strong&gt; knowledge sources, background reading driven by your interests, and citing where things came from.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Personality and feelings:&lt;/strong&gt; traits that drift slowly as you talk, Aria’s mood, and how it adapts to you over time.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Reaching out first:&lt;/strong&gt; reminders, follow-ups on things you mentioned, and checking in when it’s been a while.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Playing together:&lt;/strong&gt; games, stories and roleplay scenes.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Its voice:&lt;/strong&gt; speech in and out, and voices cloned from a short sample.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Its looks:&lt;/strong&gt; portraits, expressions that follow the conversation, pictures in chat, and an avatar that talks back.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Tools and the web:&lt;/strong&gt; looking things up, browsing safely, and asking other AI services when its own library comes up empty.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Running it:&lt;/strong&gt; the GPU servers, settings, health and diagnostics, backups, and how the code is organised.&lt;/li&gt;
&lt;/ol&gt;</content>
    <category term="ai"/>
    <category term="rag"/>
    <category term="llm"/>
    <category term="ollama"/>
    <category term="qdrant"/>
    <category term="postgresql"/>
    <category term="aspnetcore"/>
    <category term="react"/>
    <category term="docker"/>
    <category term="self-hosted"/>
    <category term="aria"/>
  </entry>
  <entry>
    <title type="html">Secure Docker Management with Traefik, Portainer and LetsEncrypt</title>
    <link href="https://www.jerhemy.com/article/2021/08/17/setup-traefik-portainer-letsencrypt/" rel="alternate" type="text/html" title="Secure Docker Management with Traefik, Portainer and LetsEncrypt"/>
    <published>2021-08-17T07:00:00+00:00</published>
    <updated>2021-08-17T07:00:00+00:00</updated>
    <id>https://www.jerhemy.com/article/2021/08/17/setup-traefik-portainer-letsencrypt/</id>
    <summary type="html">How to use Traefik, Portainer, LetsEncrypt and DigitalOcean to manage and secure your docker containers.</summary>
    <content type="html">&lt;p&gt;I recently picked up a ‘renewed’ &lt;a href=&quot;https://www.amazon.com/High-End-Dell-PowerEdge-R720-2-60Ghz/dp/B075Z3F37Z&quot;&gt;Dell Poweredge R720&lt;/a&gt;
because a colleague had gotten one and been telling me about his setup. He was running ESXi and was running multiple
services through Traefik and since I wanted to set up a couple of services for myself (Wiki.js, GitLab, and Jenkins) decided
to copy his setup. Over a couple of days, we went back and forth with him helping me get the initial setup running and working
through issues, and me sharing some additional configurations to make the overall process easier to setup. In the end,
we came up with a pretty robust and secure configuration.&lt;/p&gt;

&lt;p&gt;In the end of this, you will have robust and easy method to deploy and manage new containers, which will automatically
be configured with SSL certificate provided by LetsEncrypt and be accessible via your specified domain.&lt;/p&gt;

&lt;p&gt;This specific implementation is running on a private server on a home network, but can be easily applied to a DigitalOcean
droplet.&lt;/p&gt;

&lt;h1 id=&quot;requirements&quot;&gt;Requirements&lt;/h1&gt;
&lt;hr /&gt;
&lt;ol&gt;
  &lt;li&gt;Ubuntu Server 20.04&lt;/li&gt;
  &lt;li&gt;pfSense 2.7.0 Appliance routing your network (&lt;a href=&quot;https://www.amazon.com/Qotom-Q555G6-S05-Qotom-Industrial-Barebone-Computer/dp/B07KLZJBSZ/ref=sr_1_11?dchild=1&amp;amp;keywords=pfsense&amp;amp;qid=1629252137&amp;amp;sr=8-11&amp;amp;th=1&quot;&gt;I currently use this from Amazon&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Docker&lt;/li&gt;
  &lt;li&gt;Own the domain that you plan on using&lt;/li&gt;
  &lt;li&gt;Domain’s DNS servers are set up to use &lt;a href=&quot;https://www.digitalocean.com/&quot;&gt;DigitalOcean&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;A DigitalOcean API Key&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;what-is-traefik&quot;&gt;What is Traefik&lt;/h1&gt;
&lt;hr /&gt;

&lt;p align=&quot;center&quot;&gt;
    &lt;a href=&quot;#traefik-image&quot;&gt;
        &lt;img src=&quot;/assets/img/traefik-portainer/traefik-flow.png&quot; alt=&quot;Package Manager&quot; /&gt;
    &lt;/a&gt;
&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Traefik is a leading modern reverse proxy and load balancer that makes deploying microservices easy. Traefik integrates 
with your existing infrastructure components and configures itself automatically and dynamically.&lt;/p&gt;

  &lt;p&gt;Traefik is designed to be as simple as possible to operate, but capable of handling large, highly-complex deployments 
across a wide range of environments and protocols in public, private, and hybrid clouds. It also comes with a powerful 
set of middlewares that enhance its capabilities to include load balancing, API gateway, orchestrator ingress, as well 
as east-west service communication and more. &lt;a href=&quot;#1&quot;&gt;[1]&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It also provides a useful dashboard for monitoring and debugging routes and services registered with the traefik service.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
    &lt;img src=&quot;/assets/img/traefik-portainer/traefik-dashboard.png&quot; alt=&quot;Package Manager&quot; /&gt;
&lt;/p&gt;

&lt;h1 id=&quot;what-is-portainer&quot;&gt;What is Portainer&lt;/h1&gt;
&lt;hr /&gt;

&lt;blockquote&gt;
  &lt;p&gt;Portainer is an open source tool for managing containerized applications. It works with Kubernetes, Docker, Docker Swarm, 
Azure ACI in both data centres and at the edge.
Portainer removes the complexity associated with orchestrators so anyone can manage containers. It can be used to 
deploy and manage applications, observe the behavior of containers and provide the security and governance necessary 
to deploy containers widely. &lt;a href=&quot;#2&quot;&gt;[2]&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img src=&quot;/assets/img/traefik-portainer/portainer-dashboard.png&quot; alt=&quot;Package Manager&quot; /&gt;
&lt;/p&gt;

&lt;h1 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h1&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;install-docker&quot;&gt;Install Docker&lt;/h3&gt;

&lt;p&gt;First, update your existing list of packages:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo apt update&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Next, we need to install a few prerequisite packages which let apt use packages over HTTPS:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo apt install apt-transport-https ca-certificates curl software-properties-common&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then add the GPG key for the official Docker repository:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Add the Docker repository to APT sources:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo add-apt-repository &quot;deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable&quot;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Update the package database with the Docker packages from the repository we just added:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo apt update&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Make sure you are about to install from the Docker repository otherwise we will get it from the default Ubuntu repository:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apt-cache policy docker-ce&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We should get something similar to the output below:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Output of apt-cache policy docker-ce
docker-ce:
Installed: (none)
Candidate: 5:19.03.9~3-0~ubuntu-focal
Version table:
5:19.03.9~3-0~ubuntu-focal 500
500 https://download.docker.com/linux/ubuntu focal/stable amd64 Packages
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, install Docker:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo apt install docker-ce&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;With everything completed, we should have Docker installed and can check the status:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo systemctl status docker&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Which should output something similar:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Output
● docker.service - Docker Application Container Engine
Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
Active: active (running) since Tue 2020-05-19 17:00:41 UTC; 17s ago
TriggeredBy: ● docker.socket
Docs: https://docs.docker.com
Main PID: 24321 (dockerd)
Tasks: 8
Memory: 46.4M
CGroup: /system.slice/docker.service
└─24321 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;creating-the-docker-compose-file&quot;&gt;Creating the Docker Compose file&lt;/h3&gt;
&lt;p&gt;Lets start by setting up our skeleton &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose.yml&lt;/code&gt; file.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;3.3&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;traefik&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;traefik:v2.4&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;container_name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;traefik&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;restart&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;unless-stopped&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;networks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;networks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This sets up the image and the container name for Traefik as well as its restart policy.&lt;/p&gt;

&lt;h4 id=&quot;network&quot;&gt;Network&lt;/h4&gt;
&lt;hr /&gt;

&lt;p&gt;We will need to define a network that allows Traefik to proxy to all of our other containers. Because these containers will
not be exposed on their own, every container that Traefik will route to needs to use this network.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;na&quot;&gt;networks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;traefik-network&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;ports&quot;&gt;Ports&lt;/h3&gt;
&lt;hr /&gt;
&lt;p&gt;Here we define what ports Traefik will listen on. Even though we will only be using https, we still listen on port 80 to
respond to http requests and forward them to https.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;80:80&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;443:443&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;environments&quot;&gt;Environments&lt;/h3&gt;
&lt;hr /&gt;

&lt;p&gt;There are a couple of ways to set up the API token for DigitalOcean. This way is the most secure especially when using
Portainer as you would be able to see the API Key in plan text when looking at the container settings. Once obtaining a
API Key from DigitalOcean, you would place it in a file named ` digitalocean_api_key` in the same directory as your
docker-compose file.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;na&quot;&gt;secrets&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;digitalocean_api_key&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;DO_AUTH_TOKEN_FILE=/run/secrets/digitalocean_api_key&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;secrets&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;digitalocean_api_key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./digitalocean_api_key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you create the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;digitalocean_api_key&lt;/code&gt; file and then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ sudo chmod 600&lt;/code&gt; you will additionally secure the
api key from any other users on the computer.&lt;/p&gt;

&lt;p&gt;The simpler but less secure way is to just add a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DO_AUTH_TOKEN&lt;/code&gt; key and value.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;DO_AUTH_TOKEN=&amp;lt;key&amp;gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you do use this method, be aware that anyone with access to the Portainer dashboard will be able to see this key
in the containers’ configuration window.&lt;/p&gt;

&lt;h3 id=&quot;volumes&quot;&gt;Volumes&lt;/h3&gt;
&lt;hr /&gt;

&lt;p&gt;Not much to mention here, each of these volumes is bound to the Traefik container to facilitate automatic discovery
of new containers, store Traefik specific data and LetsEncrypt certificates.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/etc/localtime:/etc/localtime:ro&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./letsencrypt:/letsencrypt&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/var/run/docker.sock:/var/run/docker.sock:ro&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./traefik-data/traefik.yml:/traefik.yml:ro&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./traefik-data/acme.json:/acme.json\&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;command&quot;&gt;Command&lt;/h3&gt;
&lt;hr /&gt;

&lt;p&gt;These lines set up a couple of things, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;api.dashboard&lt;/code&gt; command will enabled the dashboard so we can see the routes t
hat have been configured.&lt;/p&gt;

&lt;p&gt;The provider options allow us to define additional Traefik routing for non-docker container services. This give you the
ability to have traefik secure and route to an existing service on your network.&lt;/p&gt;

&lt;h4 id=&quot;traefik-providers-settings&quot;&gt;Traefik Providers settings&lt;/h4&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--api.dashboard=true&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--providers.docker=true&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--providers.file.directory=/dynamic&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--providers.file.watch=true&quot;&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;strong&gt;entrypoints&lt;/strong&gt; commands define the different protocols that we want the services to use. In this case, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web&lt;/code&gt; and
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;websecure&lt;/code&gt; will be used when defining other containers to specify the ports to route too.&lt;/p&gt;

&lt;h4 id=&quot;setup-entry-points&quot;&gt;Setup Entry Points&lt;/h4&gt;
&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;      &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;...&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--entrypoints.web.address=:80&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--entrypoints.web.http.redirections.entryPoint.to=websecure&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--entrypoints.web.http.redirections.entryPoint.scheme=https&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--entrypoints.websecure.address=:443&quot;&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;letsencrypt-options&quot;&gt;LetsEncrypt Options&lt;/h4&gt;

&lt;p&gt;Finally we want to setup LetsEncrypt.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;This defines DigitalOcean as the provider we want LetsEncrypt to use to verify domain ownership.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;delaybeforecheck&lt;/strong&gt; does exactly what it says, and will provide a delay between the resolver making the update
to your DNS records and LetsEncrypt verifying that the records have been added.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Email that you wish too received renewal notices from LetsEncrypt&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Where we will store the LetsEncrypt data&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;What DNS servers LetsEncrypt should use when verifying DNS verification. Without this, you may run into issues
where it is using your local DNS cache and not find the records correctly.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;      &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;...&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=digitalocean&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--certificatesresolvers.letsencrypt.acme.dnschallenge.delaybeforecheck=0&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--certificatesresolvers.letsencrypt.acme.email=admin@example.com&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--certificatesresolvers.letsencrypt.acme.dnschallenge.resolvers=1.1.1.1:53,8.8.8.8:53&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;All together we should end up with something like this,&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--api.dashboard=true&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--providers.docker=true&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--providers.file.directory=/dynamic&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--providers.file.watch=true&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--providers.docker.exposedbydefault=false&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--serversTransport.insecureSkipVerify=true&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--entrypoints.web.address=:80&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--entrypoints.web.http.redirections.entryPoint.to=websecure&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--entrypoints.web.http.redirections.entryPoint.scheme=https&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--entrypoints.websecure.address=:443&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=digitalocean&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--certificatesresolvers.letsencrypt.acme.dnschallenge.delaybeforecheck=0&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--certificatesresolvers.letsencrypt.acme.email=admin@example.com&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--certificatesresolvers.letsencrypt.acme.dnschallenge.resolvers=1.1.1.1:53,8.8.8.8:53&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;labels&quot;&gt;Labels&lt;/h1&gt;

&lt;p&gt;This line sets up the user credentials to log into the Traefik dashboard. There isn’t much you can do in the interface
but it is a good place to start if you are having issues resolving a host.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;na&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;...&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;- &quot;traefik.http.middlewares.api-auth.basicauth.users=user:pass&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To get the password hash use the following. You will need to install &lt;strong&gt;apache2-utils&lt;/strong&gt; to be able to use this command.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    $ sudo apt install apache2-utils
    $ htpasswd -n username
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The other options are pretty straight forward and the only other thing to change is the &lt;strong&gt;Host&lt;/strong&gt; to be whatever URL you
want to be able to access the Traefik dashboard (ie. traefik.example.com)&lt;/p&gt;

&lt;p&gt;All together we should end up with something like this,&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;na&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.enable=true&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.middlewares.api-auth.basicauth.users=user:pass&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.routers.traefik-router.entrypoints=websecure&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.routers.traefik-router.rule=Host(`traefik.domain.tld`)&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.routers.traefik-router.tls=true&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.routers.traefik-router.tls.certresolver=letsencrypt&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.routers.traefik-router.service=api@internal&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.routers.traefik-router.middlewares=secured&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.middlewares.traefik-redirect-web-secure.redirectscheme.scheme=https&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.middlewares.secured.chain.middlewares=traefik-redirect-web-secure,api-auth&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;network-setup&quot;&gt;Network Setup&lt;/h1&gt;
&lt;hr /&gt;

&lt;p&gt;Both my colleague and I run a pfSense appliance for our network and it provides a very convenient way to route all domain
traffic to a specific IP without having to set up a separate DNS server.&lt;/p&gt;

&lt;p&gt;In your pfSense dashboard you will want to go to Services -&amp;gt; DNS Resolver and near the bottom, you will find an area
labeled &lt;strong&gt;Custom options&lt;/strong&gt;. If you don’t see that, look for the &lt;strong&gt;Display Custom Options&lt;/strong&gt; button to toggle them on.&lt;/p&gt;

&lt;p&gt;In the Custom options text field, you will want to enter the following&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;server:
local-zone: &quot;example.com&quot; redirect
local-data: &quot;example.com 3600 IN A &amp;lt;ip addr of docker server&amp;gt;&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;dynamic-traefik-routes-optional&quot;&gt;Dynamic Traefik Routes (optional)&lt;/h1&gt;
&lt;hr /&gt;

&lt;p&gt;Another great feature of Traefik is dynamic routes. When you run docker-compose, Traefik will create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dyanmic&lt;/code&gt; folder
setup by these lines in our config.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--providers.file.directory=/dynamic&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--providers.file.watch=true&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;this tells Traefik to look in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dynamic&lt;/code&gt; folder for any route files, and load them on the fly.  By placing a 
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;service-name.yaml&lt;/code&gt; file in this directory, you can specify an external service that is running on the network to route
through Traefik and still get a valid certificate.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;routers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;to-demo-service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;                      &lt;span class=&quot;c1&quot;&gt;# this is the label for the router&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;rule&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Host(`service.domain.tld`)&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;demo-service&lt;/span&gt;               &lt;span class=&quot;c1&quot;&gt;# this needs to match the name defined under `services`&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;entryPoints&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;https&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;tls&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;certResolver&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;letsencrypt&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;demo-service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;                         &lt;span class=&quot;c1&quot;&gt;# this needs to match the name defined under `routers`&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;loadBalancer&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;servers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;http://[ip&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;address]&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once you’ve added the file, you will be able to go into your Traefik dashboard and see the new route created under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Routes&lt;/code&gt;&lt;/p&gt;

&lt;h1 id=&quot;portainer-setup-optional&quot;&gt;Portainer Setup (optional)&lt;/h1&gt;
&lt;hr /&gt;

&lt;p&gt;Here is the configuration for setting up Portainer. This is also a template for any other services that you want to set up
through Traefik.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;na&quot;&gt;portainer&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;portainer/portainer:latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;container_name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;portainer&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;restart&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;unless-stopped&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;networks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;local-network&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/etc/localtime:/etc/localtime:ro&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/var/run/docker.sock:/var/run/docker.sock:ro&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./portainer-data:/data&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.enable=true&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.routers.portainer.rule=Host(`portainer.domain.tld`)&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.routers.portainer.service=portainer&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.routers.portainer.entrypoints=websecure&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.routers.portainer.tls.certresolver=letsencrypt&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.services.portainer.loadbalancer.server.port=80&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When configuring the labels for the service be sure to replace the &lt;strong&gt;labels&lt;/strong&gt; and &lt;strong&gt;values&lt;/strong&gt; with the correct entries.&lt;/p&gt;

&lt;p&gt;This template should be sufficient enough to copy &amp;amp; paste and replace &lt;strong&gt;SERVICE_NAME&lt;/strong&gt; and &lt;strong&gt;HOST.DOMAIN.TLD&lt;/strong&gt; to your specific
implementation and get it running.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.routers.&amp;lt;SERVICE_NAME&amp;gt;.rule=Host(`HOST.DOMAIN.TLD`)&quot;&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.routers.&amp;lt;SERVICE_NAME&amp;gt;.service=&amp;lt;SERVICE_NAME&amp;gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.routers.&amp;lt;SERVICE_NAME&amp;gt;.entrypoints=websecure&quot;&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.routers.&amp;lt;SERVICE_NAME&amp;gt;.tls.certresolver=letsencrypt&quot;&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.services.&amp;lt;SERVICE_NAME&amp;gt;.loadbalancer.server.port=80&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;upgrading-optional&quot;&gt;Upgrading (optional)&lt;/h1&gt;

&lt;p&gt;When new versions of Traefik or Portainer have been released you may want to upgrade to the latest version. 
This can be accomplished by using the following steps.&lt;/p&gt;

&lt;p&gt;1) Pull the latest images&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose pull&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;2) Restart containers&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose up -d --remove-orphans&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;3) Remove obsolete images (optional)&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker image prune&lt;/code&gt;&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;
&lt;hr /&gt;
&lt;p&gt;Once that your docker-compose file has been finalized and you have your DigitalOcean API key, you should be able to
simply run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo docker-compose up -d&lt;/code&gt; and after a few moments be able to go to both &lt;strong&gt;traefik.domain.tld&lt;/strong&gt; and
&lt;strong&gt;portainer.domain.tld&lt;/strong&gt; and see both sites are secured.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
    &lt;figure style=&quot;text-align: center;&quot;&gt;
        &lt;img src=&quot;/assets/img/success.jpg&quot; alt=&quot;Success&quot; /&gt;
        &lt;figcaption&gt;&quot;`Sucess!` by go digital is licensed under CC BY-SA 2.0&quot;&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;/p&gt;

&lt;h3 id=&quot;tldr&quot;&gt;TL;DR&lt;/h3&gt;
&lt;hr /&gt;
&lt;p&gt;Here is the full &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose&lt;/code&gt; for your copy and paste pleasure, be sure to either create the &lt;strong&gt;digitalocean_api_key&lt;/strong&gt;
file or switch it out with the less secure &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DO_AUTH_TOKEN&lt;/code&gt; environment option.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;3.3&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;traefik&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;traefik:v2.4&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;container_name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;traefik&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;restart&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;unless-stopped&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;networks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;local-network&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;80:80&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;443:443&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;secrets&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;digitalocean_api_key&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;DO_AUTH_TOKEN_FILE=/run/secrets/digitalocean_api_key&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/etc/localtime:/etc/localtime:ro&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./letsencrypt:/letsencrypt&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/var/run/docker.sock:/var/run/docker.sock:ro&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./traefik-data/traefik.yml:/traefik.yml:ro&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./traefik-data/acme.json:/acme.json&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--providers.docker=true&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--providers.file.directory=/dynamic&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--providers.file.watch=true&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--api.dashboard=true&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--providers.docker.exposedbydefault=false&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--entrypoints.web.address=:80&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--entrypoints.web.http.redirections.entryPoint.to=websecure&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--entrypoints.web.http.redirections.entryPoint.scheme=https&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--entrypoints.websecure.address=:443&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--serversTransport.insecureSkipVerify=true&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=digitalocean&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--certificatesresolvers.letsencrypt.acme.dnschallenge.delaybeforecheck=0&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--certificatesresolvers.letsencrypt.acme.email=admin@example.com&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--certificatesresolvers.letsencrypt.acme.dnschallenge.resolvers=1.1.1.1:53,8.8.8.8:53&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.enable=true&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.middlewares.api-auth.basicauth.users=user:pass&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.routers.traefik-router.entrypoints=websecure&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.routers.traefik-router.rule=Host(`traefik.domain.tld`)&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.routers.traefik-router.tls=true&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.routers.traefik-router.tls.certresolver=myresolver&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.routers.traefik-router.service=api@internal&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.routers.traefik-router.middlewares=secured&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.middlewares.traefik-redirect-web-secure.redirectscheme.scheme=https&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.middlewares.secured.chain.middlewares=traefik-redirect-web-secure,api-auth&quot;&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;portainer&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;portainer/portainer:latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;container_name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;portainer&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;restart&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;unless-stopped&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;networks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;local-network&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/etc/localtime:/etc/localtime:ro&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/var/run/docker.sock:/var/run/docker.sock:ro&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./portainer-data:/data&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.enable=true&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.routers.portainer.rule=Host(`portainer.domain.tld`)&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.routers.portainer.service=portainer&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.routers.portainer.entrypoints=websecure&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.routers.portainer.tls.certresolver=letsencrypt&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik.http.services.portainer.loadbalancer.server.port=80&quot;&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;networks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;local-network&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;external&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;secrets&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;digitalocean_api_key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./digitalocean_api_key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;p&gt;&lt;a id=&quot;1&quot;&gt;[1]&lt;/a&gt;
“Traefik, The Cloud Native Application Proxy | Traefik Labs.” Traefik Labs: Makes Networking Boring, 2021, &lt;a href=&quot;https://traefik.io/traefik&quot;&gt;traefik.io/traefik&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a id=&quot;2&quot;&gt;[2]&lt;/a&gt;
“Portainer | Open Source Container Management GUI for Kubernetes, Docker, Swarm.” Portaine.Io, 2021, &lt;a href=&quot;https://www.portainer.io&quot;&gt;www.portainer.io&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a id=&quot;traefik-image&quot;&gt;[3]&lt;/a&gt;
Traefik. (n.d.). [Architecture]. Retrieved August 17, 2021, from https://doc.traefik.io/traefik/&lt;/p&gt;</content>
    <category term="docker"/>
    <category term="letsencrypt"/>
    <category term="traefik"/>
    <category term="portainer"/>
    <category term="tutorials"/>
    <category term="devops"/>
  </entry>
  <entry>
    <title type="html">Setup Oculus Quest 2 and Oculus Link to run in Unity Play Mode</title>
    <link href="https://www.jerhemy.com/article/2021/05/18/unity-oculus-setup/" rel="alternate" type="text/html" title="Setup Oculus Quest 2 and Oculus Link to run in Unity Play Mode"/>
    <published>2021-05-18T07:00:00+00:00</published>
    <updated>2021-05-18T07:00:00+00:00</updated>
    <id>https://www.jerhemy.com/article/2021/05/18/unity-oculus-setup/</id>
    <summary type="html">How to set up Unity and Oculus Link for Oculus Quest 2 Development</summary>
    <content type="html">&lt;p&gt;The best tool in any developer’s belt when creating something new is having the ability to run and test changes as they go.
Initially, I was only able to test my games by running Build and Run in Unity, but that got a bit tedious, and wanted a
better way to check my work as I went along. It took going through a number of tutorials scattered around the internet to 
finally get my Oculus Quest 2 working in Unity Play Mode. This is that story…&lt;/p&gt;

&lt;h1 id=&quot;requirements&quot;&gt;Requirements&lt;/h1&gt;
&lt;hr /&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://unity.com&quot;&gt;Unity&lt;/a&gt; (We are using 2020.3.5f)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.oculus.com/setup/&quot;&gt;Oculus Link Software&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.oculus.com/accessories/oculus-link/&quot;&gt;Oculus Link Cable&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;oculus-link&quot;&gt;Oculus Link&lt;/h1&gt;
&lt;hr /&gt;
&lt;p&gt;The first thing you will want to do is install the Oculus Link application and configure your device.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Open the Oculus app on your PC.&lt;/li&gt;
  &lt;li&gt;Turn on your Quest.&lt;/li&gt;
  &lt;li&gt;Plug your USB 3 cable into a USB 3.0 port on your PC, then plug the other end into your headset.&lt;/li&gt;
  &lt;li&gt;If you are prompted with ‘Allow access to data’, select Deny.&lt;/li&gt;
  &lt;li&gt;Next, you’ll be prompted to Enable Oculus Link (beta), select Enable to begin remote rendering via Oculus Link.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;common-issues&quot;&gt;Common Issues&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;Make sure cable is secure on both computer and headset&lt;/li&gt;
  &lt;li&gt;Make sure that the Oculus Quest headset is powered on&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;unity&quot;&gt;Unity&lt;/h1&gt;
&lt;hr /&gt;

&lt;h2 id=&quot;create-project&quot;&gt;Create Project&lt;/h2&gt;

&lt;p&gt;When creating a new VR project, it is best to select the VR template from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Create a new Porject&lt;/code&gt; window. This will install
the default packages we will need to interact with our VR environment. If you do not, it is ok since we will be going
over what packages to install in the next section.&lt;/p&gt;

&lt;p&gt;Provide and Name and Location for your project and click &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Create&lt;/code&gt;.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img src=&quot;/assets/img/unity-oculus-setup/00.png&quot; alt=&quot;New Project&quot; /&gt;
&lt;/p&gt;

&lt;h2 id=&quot;install-necessary-packages&quot;&gt;Install necessary packages&lt;/h2&gt;

&lt;p&gt;One of the packages we will be using is currently in Preview, so it will not show up in the Package Manager on its own.
In order to enable preview packages, we need to enable these types of packages.&lt;/p&gt;

&lt;p&gt;Under Edit -&amp;gt; Project Settings -&amp;gt; Package Manager we will find the option ‘Enable Preview Package’ which will allow us
to see these additional options.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
    &lt;img src=&quot;/assets/img/unity-oculus-setup/05.png&quot; alt=&quot;Package Manager&quot; /&gt;
&lt;/p&gt;

&lt;p&gt;Next need to set up the XR Integration. If you have used the VR Package when creating your project you should already 
have the necessary packages installed, but it might be beneficial to check if for anything to see if there are any 
updated available.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
    &lt;img src=&quot;/assets/img/unity-oculus-setup/03.png&quot; alt=&quot;Package Manager&quot; /&gt;
&lt;/p&gt;

&lt;p&gt;The main package that is required is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Oculus XR Plugin&lt;/code&gt; while the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;XR Integration Toolkit&lt;/code&gt; is purely optional
it does provide some additional functionality to make development easier. Select each of these options and click ‘Install’
in the lower right-hand corner.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
    &lt;img src=&quot;/assets/img/unity-oculus-setup/04.png&quot; alt=&quot;Markdown Monster icon&quot; /&gt;
&lt;/p&gt;

&lt;h2 id=&quot;setup-unity-oculus-integration&quot;&gt;Setup Unity Oculus Integration&lt;/h2&gt;

&lt;p&gt;We need to now make sure that the Unity project is set up to use the Oculus for the environment.&lt;/p&gt;

&lt;p&gt;Back under ‘Edit -&amp;gt; Project Settings -&amp;gt; XR Plug-in Management’ we want to make sure that ‘Initialize XR on Startup’ is checked and 
‘Oculus’ is checked under ‘Plug-in Providers’. Be sure that you have enabled Oculus for whatever build environment you are working
with. As displayed, we are current set up for PC &amp;amp; Standalone, but if your application is set for Android, you would want to go 
to the Android Tab and make sure Oculus is selected as well.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
    &lt;img src=&quot;/assets/img/unity-oculus-setup/08.png&quot; alt=&quot;XR Plug-in Management&quot; /&gt;
&lt;/p&gt;

&lt;h2 id=&quot;run-scene&quot;&gt;Run Scene&lt;/h2&gt;
&lt;p&gt;Once everything is set up, you will need to put on your headset and open your settings menu.
On this screen you should be able to see an option labeled ‘Oculus Link’ (it may say also say ‘Rift’ underneath
but that is ok).&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
    &lt;img src=&quot;/assets/img/unity-oculus-setup/07.png&quot; alt=&quot;Markdown Monster icon&quot; /&gt;
&lt;/p&gt;

&lt;p&gt;At this point, you will be presented with a new interface and menu bar. You can now enter Unity Play Mode and be able to explore
your scene in VR. You will also want to make sure you’ve allowed any device connections between your headset and 
computer by responding to any prompts on the Oculus Quest 2.&lt;/p&gt;</content>
    <category term="unity"/>
    <category term="oculus"/>
    <category term="quest 2"/>
    <category term="game dev"/>
  </entry>
  <entry>
    <title type="html">It&apos;s a New Year</title>
    <link href="https://www.jerhemy.com/article/2021/01/01/happy-new-year/" rel="alternate" type="text/html" title="It&apos;s a New Year"/>
    <published>2021-01-01T07:00:00+00:00</published>
    <updated>2021-01-01T07:00:00+00:00</updated>
    <id>https://www.jerhemy.com/article/2021/01/01/happy-new-year/</id>
    <summary type="html">Let&apos;s see how this one goes...</summary>
    <content type="html">&lt;p&gt;It’s a New Year… yay&lt;/p&gt;

&lt;p&gt;2020 started off with so many aspirations… I’m not even going to try to make excuses. At this point the plan
is still the same but instead of setting all these goals, lets just play this one by ear.&lt;/p&gt;</content>
    <category term="Personal"/>
  </entry>
  <entry>
    <title type="html">React Component - Modal</title>
    <link href="https://www.jerhemy.com/article/2020/01/15/react-component-modal/" rel="alternate" type="text/html" title="React Component - Modal"/>
    <published>2020-01-15T07:00:00+00:00</published>
    <updated>2020-01-15T07:00:00+00:00</updated>
    <id>https://www.jerhemy.com/article/2020/01/15/react-component-modal/</id>
    <summary type="html">A simple react modal component</summary>
    <content type="html">&lt;p&gt;Throughout the course of my career in Web Development there has always been a need for modal dialog boxes, but also more importantly
the need to support accessibility. While working on a current project, the team decided to use a 3rd party modal component
to keep things moving quickly. Like with all easy things through, we ran into problems later. We discovered that the version
of the component we were using did NOT support accessibility. This was something that had been corrected since we initially 
added the component to the project, but again, like all easy things, you pay the price later. There were breaking changes in
updating the component. With all that, I held true to my philosophy of “Sometimes it is just better to write it yourself”.&lt;/p&gt;

&lt;p&gt;So here we go…&lt;/p&gt;

&lt;p&gt;This component renders a Modal component, controllable through events. 
To use the component, import Modal only once and then display it by passing a boolean value to the isVisible attribute.&lt;/p&gt;

&lt;h4 id=&quot;create-the-component&quot;&gt;Create the component&lt;/h4&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-jsx&quot; data-lang=&quot;jsx&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// modal.js&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Modal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;isVisible&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;footer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;onClose&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;React&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;keydown&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;keydownHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;removeEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;keydown&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;keydownHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]);&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;keydownHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Escape&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;onClose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;nl&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;isVisible&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;modal&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onClose&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;modal-dialog&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stopPropagation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;modal-header&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;modal-title&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;span&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;modal-close&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onClose&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;ni&quot;&gt;&amp;amp;times;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;span&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;modal-body&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;modal-content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;footer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;modal-footer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;footer&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;useEffect&lt;/code&gt; to setup an event listener on the document to listen for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;esc&lt;/code&gt; key to be pressed.
This is acting much like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;componentDidMount&lt;/code&gt; on a class-based component. Since we do not want this event listener
lingering after we are done using the component, return a function that removes the event listen which will
called when the component is destroyed.&lt;/p&gt;

&lt;h4 id=&quot;style-the-modal&quot;&gt;Style the modal&lt;/h4&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-css&quot; data-lang=&quot;css&quot;&gt;&lt;span class=&quot;o&quot;&gt;//&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;modal&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;.css&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;.modal&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;fixed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;bottom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;100%&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;z-index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;9999&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;flex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;align-items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;center&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;justify-content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;center&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rgba&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.25&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;animation-name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;animation-duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;300ms&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;.modal-dialog&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;border-radius&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;5px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;5px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;100%&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;max-width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;550px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;white&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;relative&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;20px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;max-height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;100vh&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;40px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;text-align&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;flex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;flex-direction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;overflow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;hidden&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;box-shadow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;4px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;8px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rgba&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;6px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;20px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rgba&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.19&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;-webkit-animation-name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;animatetop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;-webkit-animation-duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.4s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;animation-name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slide-in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;animation-duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.5s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;.modal-header&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;.modal-footer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;flex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;align-items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;center&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1rem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;.modal-header&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;border-bottom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1px&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;solid&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#dbdbdb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;justify-content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;space-between&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;.modal-footer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;border-top&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1px&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;solid&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#dbdbdb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;justify-content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;flex-end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;.modal-close&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;pointer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1rem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;-1rem&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;-1rem&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;-1rem&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;.modal-body&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;overflow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;.modal-content&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1rem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;@keyframes&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appear&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;opacity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;opacity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@keyframes&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slide-in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;translateY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;-150px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;translateY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;.btn&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;nl&quot;&gt;box-shadow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;inset&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;-3px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;7px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#29bbff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;#2dabf9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;nl&quot;&gt;border-radius&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;3px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;nl&quot;&gt;border&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1px&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;solid&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#0b0e07&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inline-block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;nl&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pointer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;#ffffff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;nl&quot;&gt;font-family&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Arial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;nl&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;15px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;nl&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;6px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;20px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;nl&quot;&gt;text-decoration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;nl&quot;&gt;text-shadow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#263666&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;.btn&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;:hover&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;#0688fa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;.btn&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;:active&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;nl&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;relative&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;nl&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

       &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h4 id=&quot;see-it-in-action&quot;&gt;See it in action&lt;/h4&gt;

&lt;p class=&quot;codepen&quot; data-height=&quot;265&quot; data-theme-id=&quot;default&quot; data-default-tab=&quot;js,result&quot; data-user=&quot;jswaldon&quot; data-slug-hash=&quot;jOEGoWb&quot; style=&quot;height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-pen-title=&quot;React Component: Modal&quot;&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/jswaldon/pen/jOEGoWb&quot;&gt;
  React Component: Modal&lt;/a&gt; by Jerhemy Waldon (&lt;a href=&quot;https://codepen.io/jswaldon&quot;&gt;@jswaldon&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;script async=&quot;&quot; src=&quot;https://static.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;</content>
    <category term="Web Development"/>
    <category term="React"/>
  </entry>
  <entry>
    <title type="html">Happy New Year</title>
    <link href="https://www.jerhemy.com/article/2020/01/01/happy-new-year/" rel="alternate" type="text/html" title="Happy New Year"/>
    <published>2020-01-01T07:00:00+00:00</published>
    <updated>2020-01-01T07:00:00+00:00</updated>
    <id>https://www.jerhemy.com/article/2020/01/01/happy-new-year/</id>
    <summary type="html">A New Year and a New Blog</summary>
    <content type="html">&lt;p&gt;Happy New Year!&lt;/p&gt;

&lt;p&gt;Like many of us, I too try to come up with new years resolutions that I will most likely end up giving up on in the first quarter of the new year. But I’m still going to go through the motions and say I’m going to commit to this. I’ve had this empty blog with this new layout going for months but always got distracted and never finished. But today I’m finally pushing that last commit and getting this all running.&lt;/p&gt;

&lt;p&gt;I’ve been spending a lot of the year working on building some experience in Unity, mostly with projects I end up scrapping and starting over on and want to start documenting those lessons learned. I’ve found that I’ve been starting too big like many developers do and have realized that taking a step back and starting with smaller projects will probably produce more satisfying results.&lt;/p&gt;

&lt;p&gt;Hopefully, this will be something that benefits me in the future to provide some kind of portfolio of my work and in turn also help others who are interested in learning.&lt;/p&gt;

&lt;p&gt;This is 2020!&lt;/p&gt;

&lt;div class=&quot;video-responsive&quot;&gt;
  &lt;iframe src=&quot;https://www.youtube-nocookie.com/embed/NDmgsV8aL9s&quot; title=&quot;YouTube video&quot; loading=&quot;lazy&quot; allow=&quot;accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;</content>
    <category term="Personal"/>
  </entry>
</feed>
