<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Sam Nesler</title><description>Personal website of Sam Nesler, a software engineer specializing in web development and interactive experiences. Explore my projects, blog posts, and more.</description><link>https://samnesler.com/</link><item><title>Introduction to Docker</title><link>https://samnesler.com/blog/docker/</link><guid isPermaLink="true">https://samnesler.com/blog/docker/</guid><content:encoded>&lt;p&gt;import DockerfilePlayground from &apos;@posts/docker/DockerfilePlayground.tsx&apos;;
import ImageContainerDemo from &apos;@posts/docker/ImageContainerDemo.tsx&apos;;
import DockerTerminal from &apos;@posts/docker/DockerTerminal.tsx&apos;;
import ComposeConverter from &apos;@posts/docker/ComposeConverter.tsx&apos;;&lt;/p&gt;
&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;This workshop is a group project created for the course &lt;strong&gt;COMP SCI 502&lt;/strong&gt;, Theory and Practice of Computer Science Education, at the University of Wisconsin-Madison.&lt;/p&gt;
&lt;p&gt;In this workshop, you will learn to run programs inside containers using a platform called &lt;strong&gt;Docker&lt;/strong&gt;. Docker allows developers to package applications and their dependencies into lightweight, portable containers. Think of a container as a standardized unit that includes everything needed to run a piece of software: the code, runtime, system tools, libraries, and settings.&lt;/p&gt;
&lt;p&gt;The main benefit of Docker is that it solves the classic &quot;it works on my machine&quot; problem by effectively shipping the machine alongside the program. A containerized application will run the same way whether it&apos;s on your laptop, a friend’s computer, or a production server, because the container includes the entire environment the application needs. While they have a lot in common with virtual machines, Docker containers are different: VMs include an entire operating system, but containers share a kernel with the host system. This makes containers much more lightweight and faster to start up, and you can run many containers on a single machine without the overhead of multiple full operating systems.&lt;/p&gt;
&lt;p&gt;Developers use Docker for various purposes: ensuring consistency across development and production environments, simplifying deployment, making it easier to scale applications, and isolating different applications from each other. It&apos;s become a foundational technology in modern software development, particularly in microservices architectures and cloud computing.&lt;/p&gt;
&lt;p&gt;The basic workflow involves writing a Dockerfile (which specifies how to build your container), building an image from that file, and then running containers from that image. Images can be shared through Docker Hub or other container registries, making it easy to distribute applications.&lt;/p&gt;
&lt;h1&gt;Why should I use Docker?&lt;/h1&gt;
&lt;p&gt;Pros&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reproducibility (same image ⇒ same environment)&lt;br /&gt;
An image freezes your userspace: OS base, packages, config, and entrypoint. If everyone runs &lt;code&gt;docker run myteam/app:1.3&lt;/code&gt;, you all get identical behavior.&lt;/li&gt;
&lt;li&gt;Fast startup &amp;amp; low overhead (share the host kernel)&lt;br /&gt;
Containers don’t boot a full OS; the process starts almost immediately, so iteration is quick. Compared to VMs, you use less RAM/CPU and can run many containers side-by-side.&lt;/li&gt;
&lt;li&gt;Strong isolation with easy reset (safe to break things)&lt;br /&gt;
If you break a container (even &lt;code&gt;rm -rf /&lt;/code&gt; inside it), you can remove and recreate it in seconds from the image. This makes experimentation low-risk for execution.&lt;/li&gt;
&lt;li&gt;Infrastructure-as-code with Dockerfiles.&lt;br /&gt;
A Dockerfile documents setup steps (FROM, RUN, COPY, CMD) so your environment is auditable and diff-able in Git. Builds are deterministic, and you can refactor layers for speed.&lt;/li&gt;
&lt;li&gt;Composable with Linux tooling you already know&lt;br /&gt;
Because it&apos;s just a process, you can use the standard docker command to manage it, and because the docker command is composable, you can inspect with &lt;code&gt;ps&lt;/code&gt;, manage with signals (&lt;code&gt;docker kill&lt;/code&gt; → KILL), watch I/O/CPU with &lt;code&gt;time&lt;/code&gt;/&lt;code&gt;htop&lt;/code&gt;, storage with &lt;code&gt;df -h&lt;/code&gt;/&lt;code&gt;du -sh&lt;/code&gt;, and listeners with &lt;code&gt;ss -tulpn&lt;/code&gt;. Or pipe (|) its text output to other Linux tools like grep to easily filter, automate, and script anything you need.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cons&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kernel dependency (Linux containers need a Linux kernel)&lt;br /&gt;
On Windows/macOS, Docker runs a lightweight VM to provide a Linux kernel. That layer can introduce file-sharing inconsistencies and path differences.&lt;/li&gt;
&lt;li&gt;Networking &amp;amp; ports can be confusing at first&lt;br /&gt;
Processes inside containers listen on container IPs; you must publish ports to reach them. When something &quot;isn&apos;t reachable,&quot; check &lt;code&gt;docker logs&lt;/code&gt;, &lt;code&gt;docker exec -it &amp;lt;ctr&amp;gt; ss -tulpn&lt;/code&gt;, and confirm the app bound to &lt;code&gt;0.0.0.0&lt;/code&gt; not &lt;code&gt;127.0.0.1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Excessive disk usage from layers, caches, and container&lt;br /&gt;
Builds and pulls accumulate layers, exited containers and dangling images silently consume GBs.&lt;/li&gt;
&lt;li&gt;Your responsibility for managing container resources&lt;br /&gt;
A single runaway container can exhaust CPU/RAM. Use &lt;code&gt;--cpus&lt;/code&gt;, &lt;code&gt;--memory&lt;/code&gt;, and &lt;code&gt;docker stats&lt;/code&gt; to cap usage, and prefer streaming/pipelines to reduce memory pressure. Remember: your VM may be over-booked.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Deploying your first container&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;Install docker&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER  # Add yourself to docker group
# Log out and back in for group changes to take effect
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Write your first Dockerfile&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;FROM python:3.12-slim  # Base image (your starting point)
WORKDIR /app           # Set working directory inside container
COPY app.py .
RUN pip install flask  # Execute commands during build (installs dependencies)
CMD [&quot;python&quot;, &quot;app.py&quot;]  # Default command when container starts
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Build and run&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;docker build -t my-first-app .
docker run -p 8080:5000 my-first-app
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Boom&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now let&apos;s see you give it a try yourself!&lt;/p&gt;
&lt;p&gt;&amp;lt;DockerfilePlayground client:visible /&amp;gt;&lt;/p&gt;
&lt;h1&gt;What’s the difference between an image and a container?&lt;/h1&gt;
&lt;p&gt;A Docker image is like a blueprint or template. It&apos;s a read-only package that contains everything needed to run an application: the code, runtime, libraries, environment variables, and configuration files. Think of it as a snapshot of a filesystem at a specific point in time. Images are built from Dockerfiles and stored either locally or in registries like Docker Hub.&lt;/p&gt;
&lt;p&gt;A container, on the other hand, is a running instance of an image. It&apos;s what you get when you execute &lt;code&gt;docker run&lt;/code&gt; on an image. While the image is static and immutable, a container is dynamic. The container has its own writable layer on top of the image where changes can be made during runtime. You can create multiple containers from the same image, and each will run independently with its own state. Unless you commit it to create a new image or use volumes, &lt;strong&gt;any changes made to the container&apos;s filesystem will be lost when the container is removed&lt;/strong&gt;. The writable layer exists as long as the container exists (even when stopped), but once you run &lt;code&gt;docker rm&lt;/code&gt; to remove the container, all those changes disappear.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!warning] Containers don&apos;t persist data!
For databases and other stateful applications in Docker, always use volumes to persist data beyond the container&apos;s lifecycle!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The core idea is:&lt;br /&gt;
An image is a read-only, versioned template (the recipe), it’s stateless.&lt;br /&gt;
A container is a running (or stopped) instance created from an image (the meal), it has state.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!question]- If I edit files inside a running container, does that change the image?
No. The image stays read-only; your edits live in the container’s writable layer and won&apos;t be transferred to a new container made from the same image. If you edit files in an attached &lt;strong&gt;volume&lt;/strong&gt; or &lt;strong&gt;bind mount&lt;/strong&gt;, your changes will be persisted after the container is deleted.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;figure&amp;gt;&lt;img src=&quot;../src/assets/images/posts/docker/dockerDiagram.png&quot; alt=&quot;A diagram illustrating the Docker image life cycle, showing how a Dockerfile is used to build an image, which can then be tagged, pushed to Docker Hub, pulled back down, or pruned from your system. The cycle demonstrates the relationship between local development, image registry storage, and container deployment.&quot; /&gt;&amp;lt;figcaption&amp;gt;Image credit: &lt;a href=&quot;https://decal.ocf.berkeley.edu/&quot;&gt;https://decal.ocf.berkeley.edu/&lt;/a&gt;&amp;lt;/figcaption&amp;gt;&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Here&apos;s a small interactive demo to visualize images and containers. You can &quot;create an image&quot; by building it, then &quot;run containers&quot; from that image or delete the image. All of these operations correspond to real Docker commands you would run in your terminal, which you&apos;ll learn about in the &lt;a href=&quot;#commands-to-use-and-manage-docker&quot;&gt;Commands to use and manage Docker&lt;/a&gt; section below.&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageContainerDemo client:visible /&amp;gt;&lt;/p&gt;
&lt;h2&gt;Understanding Image Layers&lt;/h2&gt;
&lt;p&gt;A Docker image is actually made up of many layers stacked on top of each other. Each instruction in a Dockerfile creates a new layer in the final image. This layered architecture is one of Docker&apos;s key innovations that makes it efficient.&lt;br /&gt;
When Docker builds an image, it executes each Dockerfile instruction in sequence:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Each RUN, COPY, ADD instruction creates a new layer&lt;/li&gt;
&lt;li&gt;Layers are read-only and cached&lt;/li&gt;
&lt;li&gt;If you rebuild an image and a layer hasn&apos;t changed, Docker reuses the cached version&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For example, consider this Dockerfile:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FROM ubuntu:latest               # Layer 1: Base image
WORKDIR /workspace               # Layer 2: Change work directory
RUN apt-get update &amp;amp;&amp;amp; apt install …  # Layer 3: Install environment
COPY . .                         # Layer 4: Copy local files
RUN make -j                      # Layer 5: Build command
CMD [&quot;make&quot;, &quot;run&quot;]           # Layer 6: Run the program, metadata (no size)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you only change your application code, Docker can reuse the cached layers 1-3 and only rebuild from layer 4 onward. This makes builds much faster! However, if you switch Layer 3 and Layer 4, because your code base has changed, starting from the changed layer, everything has to be built again. That’s why people would move layers that won’t change too often to the top of the Dockerfile.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!tip]- Optimizing Dockerfile order
Order your Dockerfile instructions from least to most frequently changed. Put dependency installation before copying source code, since code changes more often than dependencies.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Also, you can inspect how an image was built using &lt;code&gt;docker history &amp;lt;image&amp;gt;&lt;/code&gt;. However, do note that if you commit a layer, you won&apos;t see how that layer is built… So, try not to use the &lt;code&gt;commit&lt;/code&gt; command too often, especially for production.&lt;/p&gt;
&lt;h1&gt;Commands to use and manage Docker&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;[!information] Docker group and debugging notes
You shouldn&apos;t need &lt;code&gt;sudo&lt;/code&gt; if your user is in the docker group. You can add yourself to this group (on Debian or Ubuntu) by running &lt;code&gt;sudo usermod -aG docker $USER&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;If a run &quot;does nothing,&quot; you likely forgot to supply a command (e.g., bash) or the program exits immediately. To see more information, use &lt;code&gt;-it&lt;/code&gt; or check &lt;code&gt;docker logs&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Pull images&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Downloads a snapshot of installed software (an &lt;em&gt;image&lt;/em&gt;) from a registry (e.g., Docker Hub) onto your VM.
&lt;strong&gt;Why first:&lt;/strong&gt; You run containers &lt;em&gt;from&lt;/em&gt; images. No image → nothing to run.
&lt;strong&gt;Core commands&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker pull ubuntu:24.04  # fetch a specific tag/version
docker images  # list images you have
docker tag ubuntu:24.04 my-ubuntu:lts  # add a convenient name for the same image
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!info]- Docker pull auto-retrieval
If docker run references an image you don&apos;t have, Docker will try to pull it automatically.
Failures like &quot;repository does not exist or may require &apos;docker login&apos;&quot; are usually a &lt;strong&gt;typo in the image name&lt;/strong&gt;, not an auth problem.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Build images&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Creates your own image by executing scripted install steps inside a temporary build container, then snapshotting the result.
&lt;strong&gt;Why:&lt;/strong&gt; Reproducibility. Everyone else can build/get the identical environment instead of &quot;works on my machine.&quot;
&lt;strong&gt;Minimal Dockerfile&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FROM ubuntu:22.04
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y python3-pip
RUN pip3 install pandas
CMD [&quot;bash&quot;]  # default program when a container starts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Build it&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# build in the current directory (where Dockerfile lives)
docker build -t pandas .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Quick check&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker images | grep pandas
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Run containers (from images)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Starts an isolated Linux sandbox where your processes run
&lt;strong&gt;Interactive shell&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run -it ubuntu:25.10 bash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Quick check&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run ubuntu:25.10 echo &quot;hello&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Inspect and troubleshoot&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;docker ps                 # running containers
docker ps -a              # include exited
docker logs mybox         # show logs (add -f to follow)
docker exec -it mybox bash # jump into a running container
docker stop mybox         # graceful (SIGTERM then SIGKILL)
docker kill mybox         # force (SIGKILL)
docker rm mybox           # remove stopped container
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!info]- Image as class pattern
Think &quot;&lt;strong&gt;class → objects&lt;/strong&gt;&quot;: image is the class; each &lt;code&gt;docker run&lt;/code&gt; makes a new object (container).
Use &lt;code&gt;-it&lt;/code&gt; for interactive programs; &lt;code&gt;-d&lt;/code&gt; for background jobs.
Clean up exited containers periodically (&lt;code&gt;docker rm $(docker ps -aq)&lt;/code&gt;), especially when disk fills up.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Compose&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Defines and runs multi-container apps (e.g., web + db) via a single YAML file.
Read more in the &lt;a href=&quot;#docker-compose&quot;&gt;Docker Compose&lt;/a&gt; section below.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;services:
  app:
    image: python:3.12
    command: python -m http.server 8000
    ports: [&apos;8000:8000&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Quick Check&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker compose up -d    # start
docker compose ps       # status
docker compose logs -f  # follow logs
docker compose down     # stop &amp;amp; remove
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here&apos;s a playground terminal for you to try out Docker commands:&lt;/p&gt;
&lt;p&gt;&amp;lt;DockerTerminal client:visible /&amp;gt;&lt;/p&gt;
&lt;h1&gt;Docker Compose&lt;/h1&gt;
&lt;p&gt;Docker Compose is a tool for defining and running multi-container applications in a declarative way. Instead of memorizing and typing out long docker run commands with all their flags and options, you write a single YAML file that describes your entire container setup, and Compose handles spinning everything up for you. You describe what you want, and Compose figures out how to make it happen. Even better, it handles things like creating networks automatically, ensuring services can find each other by name, and managing the startup order when services depend on each other.&lt;/p&gt;
&lt;p&gt;Try it yourself! This interactive converter lets you switch between Docker Compose and docker run formats. Type in either editor to see the conversion in real-time:&lt;/p&gt;
&lt;p&gt;&amp;lt;ComposeConverter client:visible /&amp;gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!question] Wait, not everything in my Compose file is translated into Docker commands!
Some directives like &lt;code&gt;build: ./api&lt;/code&gt; or &lt;code&gt;depends_on:&lt;/code&gt; in the advanced example are Compose-specific and don&apos;t have direct &lt;code&gt;docker run&lt;/code&gt; equivalents. Compose handles build context, networks, and dependencies for you behind the scenes!&lt;/p&gt;
&lt;p&gt;If you wanted to replicate those manually, you&apos;d need to run &lt;code&gt;docker build&lt;/code&gt; separately for the build context, and wait to start dependent containers until their dependencies are healthy.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Services can reference each other by their service name. Inside the api container, you can connect to &lt;code&gt;database:5432&lt;/code&gt; rather than having to figure out IP addresses or worry about what network they&apos;re on. Compose creates a dedicated network for this application automatically.&lt;/p&gt;
&lt;p&gt;Here are some useful Compose commands:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can bring everything up with &lt;code&gt;docker compose up -d&lt;/code&gt;, which starts all services in the background. The &lt;code&gt;-d&lt;/code&gt; flag means detached mode, like with regular Docker.&lt;/li&gt;
&lt;li&gt;To see what&apos;s running, use &lt;code&gt;docker compose ps&lt;/code&gt;. This shows you all the services in your application and their status. You can get the logs from all containers with &lt;code&gt;docker compose logs -f&lt;/code&gt;, or just one service from &lt;code&gt;docker compose logs -f api&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;When you need to rebuild your images after code changes, &lt;code&gt;docker compose up --build&lt;/code&gt; will rebuild and restart any services that have changed. And when you&apos;re done, &lt;code&gt;docker compose down&lt;/code&gt; stops and removes all containers, networks, and optionally volumes with the &lt;code&gt;-v&lt;/code&gt; flag.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Docker Compose isn&apos;t meant for production orchestration at scale (that&apos;s where Kubernetes or Docker Swarm come in), but for development, testing, and simple deployments on a single host, it&apos;s an incredibly practical tool that eliminates a huge amount of manual coordination and makes your infrastructure reproducible and shareable.&lt;/p&gt;
&lt;h1&gt;Integrating with Developer Tools&lt;/h1&gt;
&lt;p&gt;This is my favorite topic to talk about, as these are the ways that I use Docker a lot.&lt;br /&gt;
Docker integrates seamlessly with modern development workflows, making it easier to maintain consistent environments across your entire team and CI/CD pipeline.&lt;br /&gt;
The key advantage of integrating with developer tools is that they eliminate the &quot;works on my machine&quot; problem. Whether you&apos;re coding locally, pushing to CI, or deploying to production, the same Docker image ensures consistency across all environments.&lt;/p&gt;
&lt;p&gt;Let’s look at some examples.&lt;/p&gt;
&lt;h2&gt;VS Code Integration&lt;/h2&gt;
&lt;p&gt;If you’re sick of typing the docker command, we recommend you to install two VS Code extensions: &lt;code&gt;Container Tools&lt;/code&gt; and &lt;code&gt;Docker DX&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Container Tools&lt;/strong&gt; (by Microsoft) adds a visual interface to manage containers, images, and volumes directly from VS Code. You can pull images, run containers, view logs, attach a terminal, and even attach VS Code (so that you don’t have to forward some ports to ssh into the container). It’s perfect for managing and debugging containers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Docker DX&lt;/strong&gt; (by Docker) focuses on authoring. It provides smart IntelliSense, syntax highlighting, and best-practice hints for Dockerfiles, Compose, and Bake files. It can even surface build warnings and vulnerabilities inline while you write.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!question] Well, what&apos;s the Docker Extension Pack then?
As of publishing, it&apos;s an extension pack that contains a single extension, making it effectively another way to download &lt;strong&gt;Container Tools&lt;/strong&gt;. So, for the best experience, it’s recommended to install &lt;strong&gt;Container Tools&lt;/strong&gt; and &lt;strong&gt;Docker DX&lt;/strong&gt; separately.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Dev Container (and Codespaces)&lt;/h2&gt;
&lt;p&gt;If you’ve ever cloned a project, spent half an hour setting up dependencies, and then discovered “it works on my machine but not yours,” &lt;strong&gt;Dev Containers&lt;/strong&gt; are here to save you.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;Dev Container&lt;/strong&gt; is basically a Docker-powered development environment defined by a simple config file (&lt;code&gt;.devcontainer/devcontainer.json&lt;/code&gt;). It tells VS Code what image to use, which extensions to install, and how to set up the workspace.&lt;/p&gt;
&lt;p&gt;You just open the folder in VS Code, and it automatically builds and launches the containerized environment. From your perspective, it feels like you’re coding on your local machine, but in fact, everything runs inside the container. That means your dependencies, compilers, and runtimes are all isolated and reproducible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Codespaces&lt;/strong&gt; takes this one step further. It’s the same idea as &lt;strong&gt;Dev Containers&lt;/strong&gt;, but hosted in the cloud by GitHub. Instead of building locally, &lt;strong&gt;Codespaces&lt;/strong&gt; spins up your container on a remote VM, preloaded with your code and environment. You can connect to it from VS Code or even use it straight in the browser. This is perfect for quick edits, demos, or when your laptop fan sounds like it’s about to take off.&lt;/p&gt;
&lt;p&gt;Here is an example of &lt;code&gt;devcontainer.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;Example Dev Container&quot;,
  &quot;image&quot;: &quot;mcr.microsoft.com/devcontainers/javascript-node:22&quot;,
  &quot;features&quot;: {
    &quot;ghcr.io/devcontainers/features/git:1&quot;: {}
  },
  &quot;forwardPorts&quot;: [3000, 5000, 5173],
  &quot;postCreateCommand&quot;: &quot;npm --version &amp;amp;&amp;amp; node --version&quot;,
  &quot;customizations&quot;: {
    &quot;vscode&quot;: {
      &quot;extensions&quot;: [&quot;dbaeumer.vscode-eslint&quot;, &quot;esbenp.prettier-vscode&quot;],
      &quot;settings&quot;: {
        &quot;editor.rulers&quot;: [120],
        &quot;editor.formatOnSave&quot;: true,
        &quot;editor.tabSize&quot;: 4
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Approach 1:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker network create myapp-network
# Start database
docker run -d \
  --name db \
  --network myapp-network \
  postgres:15

# Start web app
docker run -d \
  --name web \
  --network myapp-network \
  -p 8080:8080 \
  -e DATABASE_URL=postgresql://db:5432/mydb \
  My-web-app # Then communicate use container name as host name
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Approach 2: Docker Compose&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;docker-compose.yaml&lt;/code&gt; file:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;services:
  database:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: secret

  web:
    build: .
    ports:
      - &quot;8080:8080&quot;
    environment:
      DATABASE_URL: postgresql://database:5432/mydb
    depends_on:
      - database
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Run&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;docker compose up -d
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Then each container can communicate with another using host name and docker will automatically resolve it to the correct IP address&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>Build Your Own Portfolio Website For Free</title><link>https://samnesler.com/blog/portfolio/</link><guid isPermaLink="true">https://samnesler.com/blog/portfolio/</guid><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;This guide is a written and extended version of the workshop I wrote for &lt;a href=&quot;https://weblabs.club/&quot;&gt;WebLabs&lt;/a&gt;, a student organization run by some of my friends at the University of Wisconsin-Madison.&lt;/p&gt;
&lt;p&gt;Have you ever wanted your own website, but been put off by the limited customization options (and monthly prices) of Carrd, Wix, or Squarespace? Or maybe you&apos;ve tried to set up a blog using WordPress, but found it too complicated? It&apos;s one thing to learn basic HTML/CSS, but another thing entirely to actually deploy your work somewhere where people can see it. This workshop will help you build your own portfolio (and blog) using Astro, GitHub, and Markdown, assuming that you&apos;ve only worked with bare HTML/CSS and a bit of JS so far. You won&apos;t need anything besides a GitHub account and a browser to get started.&lt;/p&gt;
&lt;p&gt;This guide is meant to be a starting point for you to build your own portfolio website and host it for free. By &quot;your own portfolio,&quot; I mean a site that you can customize and make your own. You can add your own images, change the layout and colors, and add any pages you like. The goal is to give you the tools and knowledge to create a website that reflects your personal style and interests.&lt;/p&gt;
&lt;h1&gt;Why Even Bother?&lt;/h1&gt;
&lt;p&gt;Whether you&apos;re trying to land your first internship, link out from your socials, or just improve your skills, having a personal website that you own is incredibly valuable when everyone else is on centralized social platforms. Things like Carrd are fine, but they&apos;re limited and cost money if you want to customize them.
Beyond practical considerations, you learn a lot more by shipping stuff, rather than just reading about it or using it for homework. This guide will take you through a handful of free programs and websites that are used every day by developers in the real world, and will be invaluable for your growth as a developer. You can throw it on LinkedIn, your resume, your email signature, your cat&apos;s collar...&lt;/p&gt;
&lt;h1&gt;What You&apos;ll Need&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;A GitHub account&lt;/li&gt;
&lt;li&gt;A browser&lt;/li&gt;
&lt;li&gt;~1 hour (less if you type fast)&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Outline&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;First, we&apos;ll develop the basic website design with CodePen.&lt;/li&gt;
&lt;li&gt;Next, we&apos;ll set up the GitHub repository to host our portfolio.&lt;/li&gt;
&lt;li&gt;Then, we&apos;ll port the CodePen code to Astro and add images.&lt;/li&gt;
&lt;li&gt;After that, we&apos;ll deploy the site to GitHub Pages, which is free and easy to use.&lt;/li&gt;
&lt;li&gt;Then we&apos;ll drop into GitHub Codespaces to add a blog to our portfolio using Astro&apos;s content collections.&lt;/li&gt;
&lt;li&gt;Finally, I&apos;ll suggest some ways to improve your portfolio and blog, and give you some resources to learn more about Astro and web development in general.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Step 1: Mess Around on CodePen&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;../src/assets/images/portfoliotemplate.png&quot; alt=&quot;A screenshot of a blank portfolio website&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://go.weblabs.club/portfolio-js&quot;&gt;go.weblabs.club/portfolio-js&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Get a feel for how your portfolio might look:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Don&apos;t worry about images yet.&lt;/li&gt;
&lt;li&gt;In the HTML panel, fill in your name, bio, links, skills, projects.&lt;/li&gt;
&lt;li&gt;Use the CSS variables to change colors and fonts, if you like.&lt;/li&gt;
&lt;li&gt;Once you&apos;re happy with it, we&apos;ll move onto the next step.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Step 2: Clone the GitHub Template&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://go.weblabs.club/portfolio-github&quot;&gt;go.weblabs.club/portfolio-github&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This template repository is essentially what you get by running &lt;code&gt;npm create astro@latest&lt;/code&gt; and selecting the basic template, but I ripped out the parts that are unnecessary for this workshop. It doesn&apos;t include anything besides the bare minimum configuration, we&apos;ll be adding everything else as we go.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Sign in to GitHub.&lt;/li&gt;
&lt;li&gt;Use the template to make a repo named &lt;code&gt;${your-username}.github.io&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is where your site will live. Beyond storing code, GitHub offers several other useful services like GitHub Pages, GitHub Actions, and GitHub Codespaces. In this case, GitHub Pages will host your site at that URL later.&lt;/p&gt;
&lt;h1&gt;Step 3: CodePen, meet Astro&lt;/h1&gt;
&lt;p&gt;Astro is static site builder with &lt;em&gt;zero&lt;/em&gt; JS by default, unless you opt in. It&apos;s perfect for beginners that want a little extra sauce out of their HTML. You&apos;ll get components (like React) but no runtime overhead, and you can do cool things like Markdown-based blogs without needing a CMS.&lt;/p&gt;
&lt;h2&gt;Porting to Astro&lt;/h2&gt;
&lt;p&gt;Your HTML/CSS from CodePen drops in &lt;em&gt;almost&lt;/em&gt; unchanged.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open &lt;code&gt;src/pages/index.astro&lt;/code&gt; on GitHub and select the pencil (edit) button.&lt;/li&gt;
&lt;li&gt;Paste your CodePen HTML between the &lt;code&gt;&amp;lt;Layout&amp;gt;&lt;/code&gt; tags.&lt;/li&gt;
&lt;li&gt;Paste your CSS in a &lt;code&gt;&amp;lt;style is:global&amp;gt;&lt;/code&gt; block after the HTML.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;---
import Layout from &apos;../layouts/Layout.astro&apos;;
---

&amp;lt;!-- Example --&amp;gt;
&amp;lt;Layout&amp;gt;
  &amp;lt;!-- Your HTML here --&amp;gt;
&amp;lt;/Layout&amp;gt;

&amp;lt;style is:global&amp;gt;
  /* Your CSS here */
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!question]- What&apos;s the deal with &lt;code&gt;is:global&lt;/code&gt;?
The &lt;code&gt;is:global&lt;/code&gt; attribute &lt;a href=&quot;https://docs.astro.build/en/reference/directives-reference/#isglobal&quot;&gt;tells Astro that this CSS should be applied globally, rather than scoped to the component&lt;/a&gt;. This is useful for styles that you want to apply to the entire page, like your main layout or global styles. In this case, you might later decide to break apart the page into components, and using &lt;code&gt;is:global&lt;/code&gt; simplifies that process a bit since you don&apos;t have to disentangle which styles need to be copied over to the component.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now for deployment:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;On GitHub, go to the &lt;strong&gt;Actions&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;Search for “Astro”, click to enable the action.&lt;/li&gt;
&lt;li&gt;Then go to &lt;strong&gt;Settings &amp;gt; Pages&lt;/strong&gt; and choose the GitHub Action as your source.&lt;/li&gt;
&lt;li&gt;Wait a few seconds, then go to &lt;code&gt;https://your-username.github.io&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&apos;s live, and it&apos;s that simple.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!question]- Why do the icons still work?
Okay, you got me. I cheated a bit, this template does include the same FontAwesome stylesheet that the CodePen template does. You can see it hiding in &lt;code&gt;src/layouts/Layout.astro&lt;/code&gt; in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section. &lt;code&gt;Layout.astro&lt;/code&gt; is a component that wraps around your HTML, and it includes the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section for you. You can also add your own stylesheets or other scripts in there, if you want.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!question]- What is GitHub Actions?
GitHub Actions is a way to automate tasks in your GitHub repository, like running tests, building your code, or deploying your site. It essentially lets you briefly borrow a completely clean computer to run your code, and then throw it away when you&apos;re done. In this case, we&apos;re using it to build our Astro site (which needs to run code to build) and deploy it to GitHub Pages. You can think of it as a way to run your code in the cloud, without needing to set up a server or anything like that.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!question]- How can GitHub pages be free?
GitHub Pages only works for static files (so just HTML, CSS, JS and images). This means that you can&apos;t run any server-side code (like PHP or Node.js) on GitHub Pages, because GitHub doesn&apos;t run a server for you. Instead, your GitHub action will produce a handful of those HTML/CSS files, which GitHub stores up on a CDN, and your visitor&apos;s browsers do the work of rendering them. Serving static files is cheap enough that GitHub can afford to do it for free.&lt;/p&gt;
&lt;p&gt;Also, it&apos;s technically only free for public repositories, which means that anyone can see your code. GitHub does this partially because they want to encourage open-source development and collaboration, and partially because they&apos;re owned by Microsoft and can eat the cost.&lt;/p&gt;
&lt;p&gt;CloudFlare is another company that does free static hosting, and in their case they serve something like 20% of all websites through their CDN. In their case, the marginal cost of storing a couple megabytes of data on the CDN that other people are already paying for is negligible, so they can afford to give it away for free.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;Step 4: Add Images&lt;/h1&gt;
&lt;p&gt;Let&apos;s add some photos while we&apos;re here. You can do this from the GitHub web interface as well.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Upload your image files to &lt;code&gt;/public&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Refer to them like:&lt;pre&gt;&lt;code&gt;&amp;lt;img src=&quot;/myimage.png&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
No &lt;code&gt;public/&lt;/code&gt; prefix, just the slash.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!question]- Why no &lt;code&gt;public/&lt;/code&gt;?
The &lt;code&gt;public&lt;/code&gt; folder is a special folder in Astro, and most JS frontend frameworks. Anything in there isn&apos;t processed, and instead placed at the root of your final site, so &lt;code&gt;/public/myimage.png&lt;/code&gt; will become just &lt;code&gt;/myimage.png&lt;/code&gt; after building. This is different from other frameworks like React, where you have to use &lt;code&gt;public/&lt;/code&gt; in the path.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;Step 5: Add Blog with Content Collections&lt;/h1&gt;
&lt;p&gt;This part would have been a live demo in the in-person workshop, so it&apos;s a little more informal, and there unfortunately are no template repos for you to copy. Now that we&apos;ve got a wonderful portfolio site with all our projects and images, let&apos;s add a blog to it. This is a great way to show off your skills and share resources (like this workshop) with the world.&lt;/p&gt;
&lt;p&gt;Astro&apos;s content collections allow you to create a blog with Markdown files. You can write your posts in (effectively) plain text, and Astro will take care of the rest. We&apos;ll also be using something called &quot;frontmatter&quot; to add metadata to your posts, like the title, date, and image.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!QUESTION]- What is Markdown?
Markdown is a lightweight markup language that allows you to write formatted text in plain text files. Markdown files are typically saved with a &lt;code&gt;.md&lt;/code&gt; extension and can be easily converted to HTML. As a result, they&apos;re widely used for writing content on the web. Some places you might&apos;ve seen Markdown are GitHub READMEs, Discord, Reddit, Notion, or Obsidian. It&apos;s a great way to write content without spending lots of time fussing with HTML tags.&lt;/p&gt;
&lt;p&gt;Astro&apos;s markdown support uses specifically &lt;a href=&quot;https://github.github.com/gfm/&quot;&gt;GitHub-flavored Markdown&lt;/a&gt;, which is a superset of the original Markdown spec.&lt;/p&gt;
&lt;p&gt;This blog post is written in Markdown, and you can see how it looks in the &lt;a href=&quot;https://github.com/the-snesler/samnesler.com/tree/main/posts/portfolio.mdx&quot;&gt;source code&lt;/a&gt;! You&apos;ll notice the extension is &lt;code&gt;.mdx&lt;/code&gt;, which is a special version of Markdown that allows you to use React components inside your Markdown files. This post doesn&apos;t use that feature, but Astro supports MDX too.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;1. Open up GitHub Codespaces&lt;/h2&gt;
&lt;p&gt;We&apos;ve been getting by on GitHub&apos;s web interface so far, but now we need to do some more complicated things. GitHub Codespaces is a way to run a full VSCode instance in your browser, and you get a certain number of hours for free. It&apos;s plenty for this use-case, but you can also use VSCode locally if you prefer.&lt;/p&gt;
&lt;p&gt;You can open a GitHub Codespace by clicking the &quot;Code&quot; tab at the top of your repo, then clicking the green &quot;Code&quot; button above your files, then the &quot;Codespaces&quot; tab within the popup that appears. Finally, select &quot;Create Codespace on main&quot;. This will create a new Codespace for you, which is a full VSCode instance running in your browser. It&apos;ll also download your repo dependencies, so all you need to do is wait a minute or so for it to load, then type &lt;code&gt;npm run dev&lt;/code&gt; in the terminal to see a live preview of your site.&lt;/p&gt;
&lt;h2&gt;2. Define Your Blog Schema&lt;/h2&gt;
&lt;p&gt;Astro&apos;s content collections are a way to define the structure of your content. You can think of them sort of like creating a database for your blog posts. Each post will have a title, date, and other metadata that we&apos;ll use later to display your posts on the site.&lt;/p&gt;
&lt;p&gt;Create &lt;code&gt;src/content/config.ts&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { defineCollection, z } from &apos;astro:content&apos;;

const blogCollection = defineCollection({
  type: &apos;content&apos;,
  schema: z.object({
    title: z.string(),
    date: z.date(),
    excerpt: z.string(),
    image: z.string().optional()
  })
});

export const collections = {
  blog: blogCollection
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. Write Some Posts&lt;/h2&gt;
&lt;p&gt;Create at least one Markdown file in &lt;code&gt;src/content/blog/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/content/blog/first-post.md&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
title: &apos;First Blog Post&apos;
date: 2025-04-16
excerpt: &apos;A quick dive into making your first blog post in Astro.&apos;
---

Here&apos;s your actual blog content. Markdown works here!
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!question]- What is frontmatter?
You&apos;ll notice that the Markdown file starts with a block of text between &lt;code&gt;---&lt;/code&gt; lines. This is called &quot;frontmatter&quot;, and it&apos;s a way to add metadata to your Markdown files. The frontmatter is written in YAML, which is a human-readable data format. Think of it a bit like JSON. In this case, we&apos;re using it to define the title, date, and excerpt for our blog post. Astro will use this frontmatter to display the title, date, and excerpt elsewhere on the site. You can also use frontmatter to define other metadata, like tags, categories, and more.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;4. Show Posts on the Main Site&lt;/h2&gt;
&lt;p&gt;Back in your &lt;code&gt;src/pages/index.astro&lt;/code&gt;, import your blog collection at the top of the file (the frontmatter area that was YAML in Markdown files is JavaScript in Astro):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
import { getCollection } from &apos;astro:content&apos;;
const posts = await getCollection(&apos;blog&apos;);
const sortedPosts = posts.sort((a, b) =&amp;gt; b.data.date.getTime() - a.data.date.getTime()); // sorts by date, if you like
---
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, replace the dummy blog section further down the file with this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;section id=&quot;blog&quot; class=&quot;blog&quot;&amp;gt;
  &amp;lt;div class=&quot;container&quot;&amp;gt;
    &amp;lt;h2&amp;gt;Blog&amp;lt;/h2&amp;gt;
    &amp;lt;p&amp;gt;Thoughts, tutorials, and insights&amp;lt;/p&amp;gt;
    &amp;lt;div class=&quot;blog-grid&quot;&amp;gt;
      {
        sortedPosts.map(post =&amp;gt; (
          &amp;lt;div class=&quot;blog-card&quot;&amp;gt;
            &amp;lt;div class=&quot;blog-date&quot;&amp;gt;{post.data.date.toLocaleDateString(&apos;en-US&apos;)}&amp;lt;/div&amp;gt;
            &amp;lt;h3&amp;gt;{post.data.title}&amp;lt;/h3&amp;gt;
            &amp;lt;p&amp;gt;{post.data.excerpt}&amp;lt;/p&amp;gt;
            &amp;lt;a href={`/blog/${post.slug}`}&amp;gt;
              Read More &amp;lt;i class=&quot;fa-solid fa-arrow-right&quot; /&amp;gt;
            &amp;lt;/a&amp;gt;
          &amp;lt;/div&amp;gt;
        ))
      }
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/section&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&apos;ll notice that, like React, Astro uses &lt;code&gt;{}&lt;/code&gt; to interpolate JavaScript into your HTML. This is a great way to dynamically generate content based on your data. In this case, we&apos;re using it to loop through the blog posts and display them in a grid. Note that &lt;em&gt;unlike&lt;/em&gt; React, this JavaScript will only run &lt;em&gt;once&lt;/em&gt; at build-time, so you shouldn&apos;t try to make it interactive.&lt;/p&gt;
&lt;p&gt;You&apos;ll also notice that we&apos;re linking to &lt;code&gt;/blog/${post.slug}&lt;/code&gt;. Let&apos;s go create that page now!&lt;/p&gt;
&lt;h2&gt;5. Create a Template for Individual Posts&lt;/h2&gt;
&lt;p&gt;Make &lt;code&gt;src/pages/blog/[...slug].astro&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
import { getCollection } from &apos;astro:content&apos;;
import Layout from &apos;../../layouts/Layout.astro&apos;;

// Generate paths for all blog posts
export async function getStaticPaths() {
  const blogEntries = await getCollection(&apos;blog&apos;);
  return blogEntries.map(entry =&amp;gt; ({
    params: { slug: entry.slug },
    props: { entry }
  }));
}

// Get the blog post content
const { entry } = Astro.props;
// Markdown is text, so we need to &quot;render&quot; it to transform it into HTML
const { Content } = await entry.render();
---

&amp;lt;Layout&amp;gt;
  &amp;lt;article class=&quot;blog-post container&quot;&amp;gt;
    &amp;lt;h1&amp;gt;{entry.data.title}&amp;lt;/h1&amp;gt;
    &amp;lt;time datetime={entry.data.date.toISOString()}&amp;gt;
      {
        entry.data.date.toLocaleDateString(&apos;en-US&apos;, {
          year: &apos;numeric&apos;,
          month: &apos;long&apos;,
          day: &apos;numeric&apos;
        })
      }
    &amp;lt;/time&amp;gt;
    &amp;lt;div class=&quot;blog-content&quot;&amp;gt;
      &amp;lt;Content /&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;a href=&quot;/#blog&quot;&amp;gt;← Back to Blog&amp;lt;/a&amp;gt;
  &amp;lt;/article&amp;gt;
&amp;lt;/Layout&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now if you go to &lt;code&gt;/blog/first-post&lt;/code&gt; (or whatever you named your markdown file), you should see your blog post!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!question]- What is &lt;code&gt;getStaticPaths&lt;/code&gt;?
Astro does all its work at build time (inside your GitHub Action), so it needs to know what pages to generate ahead of time. &lt;code&gt;getStaticPaths&lt;/code&gt; is a way to tell Astro which pages to generate based on your data. In this case, we&apos;re using it to generate a page for each blog post in our collection. You can think of it as a way to &quot;pre-render&quot; your pages at build time, rather than waiting for the user to request them, since we aren&apos;t allowed to run any server-side code on GitHub Pages.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;6. Commit Your Hard Work&lt;/h2&gt;
&lt;p&gt;Let&apos;s commit your changes in Codespaces, and push them to GitHub, so your Action can run and your site will be live! You can do that by selecting the &quot;Source Control&quot; tab (looks like a few nodes connected by lines, and probably has a notification icon on it) on the left sidebar, then clicking the &quot;+&quot; icon next to each file you changed. Then, type a commit message (like &quot;Added blog&quot;) and click the &quot;Commit&quot; button to commit your changes. Finally, click the &quot;Sync Changes&quot; button at the top of the sidebar to push your changes to GitHub.&lt;/p&gt;
&lt;p&gt;Wait a minute, then refresh your site. You should see your blog posts on the homepage, and if you click on one, it should take you to the individual post page!&lt;/p&gt;
&lt;h1&gt;You&apos;re Done (But You Should Totally Keep Going)&lt;/h1&gt;
&lt;p&gt;This is a great start, but there&apos;s so much more you can do with Astro. Here are some ideas and links to get you started:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The blog post page is... lightly themed, to say the least. You could totally spruce it up a bit.&lt;/li&gt;
&lt;li&gt;Maybe you don&apos;t want all your posts on the home page. You can add another page that just lists your blog posts!&lt;/li&gt;
&lt;li&gt;Tailwind is a common way to add some styling to sites in a more terse form than straight CSS. You can &lt;a href=&quot;https://docs.astro.build/en/guides/integrations-guide/tailwind/&quot;&gt;add Tailwind to Astro&lt;/a&gt; in just a few minutes, and it makes it super easy to add responsive styles and custom themes.&lt;/li&gt;
&lt;li&gt;If you want to add reactivity, you can add frontend frameworks like React and Svelte to Astro with just a few commands. Astro is designed to work with these frameworks, so you can use them to build interactive components without needing to set up a whole React or Svelte app. You can also use MDX to write your blog posts in Markdown and include React components inside them.&lt;/li&gt;
&lt;li&gt;You could set up &lt;a href=&quot;https://giscus.app/&quot;&gt;Giscus&lt;/a&gt; for a free commenting system on your blog posts. You can see an example of it on this very page! Giscus is a free, open-source commenting system that uses GitHub Discussions to store comments. It&apos;s a great way to add comments to your blog posts without needing to set up a whole backend or database. You can also use it to get feedback on your posts and engage with your readers.&lt;/li&gt;
&lt;li&gt;Having the &lt;code&gt;username.github.io&lt;/code&gt; URL is a bit lame. You can &lt;a href=&quot;https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site&quot;&gt;set up a custom domain&lt;/a&gt;, but you&apos;ll have to pay for one. They&apos;re usually less than $15/year, though, so it&apos;s worth it if you want to continue doing things on the web. &lt;a href=&quot;https://tld-list.com/&quot;&gt;tld-list.com&lt;/a&gt; is a good way to search for domains that aren&apos;t already registered!&lt;/li&gt;
&lt;li&gt;If you use Discord, you might consider setting up my &lt;a href=&quot;https://github.com/the-snesler/discord-github-preview&quot;&gt;Discord GitHub Preview project&lt;/a&gt; :). It&apos;ll show your Discord activity in an SVG that you can place in an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag. This is a great way to show off your activity and make your site more dynamic.&lt;/li&gt;
&lt;li&gt;If you plan to post a lot on your blog, you might set up &lt;a href=&quot;https://docs.astro.build/en/recipes/rss/&quot;&gt;an RSS feed&lt;/a&gt; so people can subscribe to it. This is a great way to keep your readers updated on your latest posts, and it&apos;s a good way to learn about RSS feeds and XML. Astro also makes this dead simple.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Things I Use</title><link>https://samnesler.com/blog/uses/</link><guid isPermaLink="true">https://samnesler.com/blog/uses/</guid><content:encoded>&lt;p&gt;Here&apos;s a list of the tools, software and many other things that I use on a daily basis. This page was inspired by Wes Bos and his project - &lt;a href=&quot;https://uses.tech&quot;&gt;uses.tech&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Web&lt;/h1&gt;
&lt;h2&gt;Browser&lt;/h2&gt;
&lt;p&gt;It used to be &lt;a href=&quot;https://www.firefox.com/en-US/&quot;&gt;Firefox&lt;/a&gt; with lots of extensions to make it look and feel like &lt;a href=&quot;https://arc.net/&quot;&gt;Arc&lt;/a&gt;, but then &lt;a href=&quot;https://zen-browser.app/&quot;&gt;Zen&lt;/a&gt; came out and I switched to it.&lt;/p&gt;
&lt;p&gt;Zen is an open-source Firefox fork with many of the same UI features as Arc, and it&apos;s still receiving support (unlike Arc, RIP). It also works better on my Windows desktop and laptop than Arc did.&lt;/p&gt;
&lt;h2&gt;Extensions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/augmented-steam/&quot;&gt;Augmented Steam&lt;/a&gt; - Enhances Steam with additional information&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/bitwarden-password-manager/&quot;&gt;Bitwarden&lt;/a&gt; - Excellent password manager&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/darkreader/&quot;&gt;Dark Reader&lt;/a&gt; - Auto dark mode for every website (helpful for battery life on my OLED laptop)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/imagus-mod/&quot;&gt;Imagus Mod&lt;/a&gt; - Image hover zoom (useful for Reddit)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/ublock-origin/&quot;&gt;uBlock Origin&lt;/a&gt; - One ad blocker to rule them all&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Websites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://cobalt.tools/&quot;&gt;Cobalt&lt;/a&gt; - YouTube video downloader that I don&apos;t personally use but would be remiss not to mention (see Vividl below!)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gchq.github.io/CyberChef/&quot;&gt;CyberChef&lt;/a&gt; - A web app for encoding, decoding, and analyzing text data in a variety of formats&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://devdocs.io/&quot;&gt;DevDocs&lt;/a&gt; - A frontend for various language/library documentation sites with quick search.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hoppscotch.io/&quot;&gt;Hoppscotch&lt;/a&gt; - A Postman alternative PWA, great for quick API testing&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://isthereanydeal.com/&quot;&gt;IsThereAnyDeal&lt;/a&gt; - Great for tracking game prices and bundles, with an excellent waitlist feature&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/maxence-charriere/lofimusic&quot;&gt;LofiMusic&lt;/a&gt; - A music player PWA for lo-fi music YouTube livestreams&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mynoise.net/&quot;&gt;MyNoise&lt;/a&gt; - A fantastic noise generator that I use for focus and relaxation&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.pocketcasts.com/&quot;&gt;Pocket Casts&lt;/a&gt; - My podcast app of choice (partially because I bought the web app before they went a subscription model)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://winstall.app/&quot;&gt;Winstall&lt;/a&gt; - A WinGet command generator that makes it easy to install a bunch of apps at once, like Ninite on steroids&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wormhole.app/&quot;&gt;Wormhole&lt;/a&gt; - A dead simple, super fast way to send &amp;lt;10gb files between devices. Great for sharing things with friends, and it&apos;s been around for a while so I&apos;m confident it won&apos;t disappear.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Windows&lt;/h1&gt;
&lt;p&gt;I have a desktop and laptop that I love dearly.&lt;/p&gt;
&lt;p&gt;import Card from &apos;@/components/cards/Card.astro&apos;;&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;grid gap-4 md:grid-cols-2&quot;&amp;gt;
&amp;lt;Card&amp;gt;&lt;strong&gt;Desktop&lt;/strong&gt; OS: Windows 11 CPU: Intel Core i5-10600KF GPU: Nvidia RTX 4060 Ti 16gb&amp;lt;/Card&amp;gt;
&amp;lt;Card&amp;gt;&lt;strong&gt;Laptop&lt;/strong&gt; OS: Windows 11 Model: ASUS Zenbook CPU: AMD Ryzen 7-7730U&amp;lt;/Card&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h2&gt;Windows Apps&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://affinity.serif.com/en-us/&quot;&gt;Affinity Suite&lt;/a&gt; - Sometimes I want to do some vector graphics work or image editing, but I can&apos;t justify an Adobe subscription and I&apos;m not going to admit to pirating it. Affinity is a great alternative! I&apos;ve been with them for years now, since I initially bought Affinity Designer on sale for $25 (I was previously making YouTube thumbnails in Apple Keynote, of all things)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/microsoft/PowerToys&quot;&gt;Microsoft Powertoys&lt;/a&gt; - A collection of utilities you&apos;d be be a tool to miss.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://calendar.notion.so&quot;&gt;Notion Calendar&lt;/a&gt; - Just a nice calendar app. Since the rebrand, it can also pull in assignments from my Notion database and display them on my calendar too.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.notion.so/&quot;&gt;Notion&lt;/a&gt; - Notion is great for organizing tasks in just the right way for me, and it can hold notes and my game backlog too. It has a decent enough API, so when combined with a self-hosted &lt;a href=&quot;https://n8n.io/&quot;&gt;n8n&lt;/a&gt; instance, I can freely write my own little extensions to sync Canvas assignments, Todoist tasks, and game backlog data from outside sources. In theory, I could do much of the same things with Obsidian too, but the ease of syncing, sharing, and extending with Notion win out.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://playnite.link/&quot;&gt;PlayNite&lt;/a&gt; - If you&apos;re anything like me, you&apos;ve been accumulating free games for years from &lt;a href=&quot;https://gaming.amazon.com/&quot;&gt;Amazon Prime&lt;/a&gt;, &lt;a href=&quot;https://www.epicgames.com/store/en-US/free-games&quot;&gt;Epic Games&lt;/a&gt;, and other sources. Combine that with a habit of &quot;buying games wherever they&apos;re cheapest&quot; and owning console games, and you end up with a mess of launchers and libraries. PlayNite is a free, open-source app that lets you manage all your games in one place, no matter where they came from. It also has a great plugin system that lets you add features like game tracking, achievements, and more. I find it more robust and extensible than &lt;a href=&quot;https://www.gog.com/galaxy&quot;&gt;GOG Galaxy&lt;/a&gt;, though that&apos;s a great option too.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://raycast.com/&quot;&gt;Raycast&lt;/a&gt; - My launcher of choice, recently in beta for Windows. I loved it back on my Mac, and I feel it beats PowerToys Run and even the new Windows Command Palette in terms of speed and stability. It already has a great library of extensions, and I&apos;m excited for future development!&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://getsharex.com/&quot;&gt;ShareX&lt;/a&gt; - Screenshotting and screen recording app.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sparkmailapp.com/&quot;&gt;Spark&lt;/a&gt; - As far as I know, Spark is the only 1) nice-looking 2) native Windows mail app that is 3) free and has 4) automatic categories.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sourceforge.net/projects/vividl/&quot;&gt;Vividl&lt;/a&gt; - Great free YouTube video downloader. Functionally a frontend for yt-dlp, so it doesn&apos;t &lt;em&gt;usually&lt;/em&gt; break.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/amnweb/yasb&quot;&gt;Yasb&lt;/a&gt; - A highly configurable Windows status bar written in Python. Between this, Raycast, and Zen, I wonder if I should just go back to MacOS?&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Homelab&lt;/h1&gt;
&lt;p&gt;This is how I got into programming in the first place. There&apos;s no magic quite like wiring up 6 Docker containers on a $150 Windows box and getting a website out of it, and I&apos;ve continued that for about 8 years to my still-modest current setup.&lt;/p&gt;
&lt;p&gt;&amp;lt;Card&amp;gt;&lt;strong&gt;Inkstation&lt;/strong&gt; OS: OpenMediaVault CPU: Intel i5-2500S Uptime: too long&amp;lt;/Card&amp;gt;&lt;/p&gt;
&lt;h2&gt;Homelab Services&lt;/h2&gt;
&lt;p&gt;A homelab is nothing without something for it to host. These ones are my favorites, either because they&apos;re the best of the alternatives I tried or they&apos;re just really cool:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.authelia.com/&quot;&gt;Authelia&lt;/a&gt; - No-nonsense auth provider that makes securing other apps straightforward.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://goauthentik.io/&quot;&gt;Authentik&lt;/a&gt; - ...that said, Authentik is harder to set up but way more flexible.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bluemap.bluecolored.de/&quot;&gt;Bluemap&lt;/a&gt; - Serves 3D maps of Minecraft worlds over the web. Check out &lt;a href=&quot;https://mcarchive.tsuni.dev&quot;&gt;past worlds&lt;/a&gt; and &lt;a href=&quot;https://mc.tsuni.dev&quot;&gt;maybe even my current server&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://caddyserver.com/&quot;&gt;Caddy&lt;/a&gt; - A reverse proxy that&apos;s much simpler to set up than nginx or Traefik.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/lucaslorentz/caddy-docker-proxy&quot;&gt;Caddy-Docker-Proxy&lt;/a&gt; - Wonderful Caddy extension that makes deploying new containers to a subdomain as &lt;a href=&quot;https://gist.github.com/the-snesler/11dcda9bbbccb56416f773ddda376b99&quot;&gt;easy as 2 labels on a container&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/janeczku/calibre-web&quot;&gt;Calibre-web&lt;/a&gt; - Drop-in replacement for my Kobo e-reader&apos;s sync server, allowing me to get books to it from anywhere.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://schollz.com/tinker/croc/&quot;&gt;Croc&lt;/a&gt; - Super simple CLI file transfer tool that handles discovery for you. Nice for university computers.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://n8n.io/&quot;&gt;n8n&lt;/a&gt; - Node-based automation platform. All the little things that Zapier would be helpful for, but free.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://plausible.io/&quot;&gt;Plausible&lt;/a&gt; - Analytics that are super easy to just slot into any project&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.plex.tv/&quot;&gt;Plex&lt;/a&gt; - Media server extraordinaire. Music support is better than Jellyfin&apos;s equivalent, imo.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pterodactyl.io/&quot;&gt;Pterodactyl&lt;/a&gt; - Host and manage many game servers, with less management work than running them bare.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.seafile.com/en/home/&quot;&gt;Seafile&lt;/a&gt; - File storage with rock-solid sync, and direct-download share links&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tailscale.com/&quot;&gt;Tailscale&lt;/a&gt; - Allows me to access this server and my 3 VPSes without exposing them to the web. Taildrop is great for iOS and Windows file transfer, especially when the network doesn&apos;t allow LAN devices to talk to each other.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item></channel></rss>