<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Itay Wolfish</title><description>Senior full-stack and devops engineer in Tel Aviv. Writing about infrastructure, tooling, and shipping with a boring stack.</description><link>https://itaywol.com/</link><language>en</language><atom:link href="https://itaywol.com/rss.xml" rel="self" type="application/rss+xml"/><item><title>No backend, but a real database</title><link>https://itaywol.com/blog/no-backend-but-a-real-database/</link><guid isPermaLink="true">https://itaywol.com/blog/no-backend-but-a-real-database/</guid><description>How I shipped a client-side app with SQL persistence, product analytics, and live external data — without operating a single server. The architecture behind Scrolls Buddy.</description><pubDate>Sat, 04 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I built &lt;a href=&quot;https://itaywol.com/projects/scrolls-assistant/&quot;&gt;Scrolls Buddy&lt;/a&gt;, a planning tool for a MapleStory
private-server community. The audience is about twenty people a day. That number is the
whole story of the architecture, because it made one decision for me before I wrote any
code: &lt;strong&gt;there would be no backend.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Not &quot;a small backend.&quot; None. No server to secure, no database to operate, no bill that
grows with traffic. The interesting engineering was refusing to give up any real
capability to honour that — because &quot;no backend&quot; usually means &quot;no persistence, no
analytics, no data,&quot; and I wanted all three.&lt;/p&gt;
&lt;p&gt;Here is how each one survived the constraint.&lt;/p&gt;
&lt;h2&gt;Persistence: SQLite in the browser&lt;/h2&gt;
&lt;p&gt;The app has session history, saved item configurations, and scrolling outcomes I want to
query — &quot;show me this session&apos;s profit,&quot; &quot;what did I roll last time.&quot; That is relational
data, and &lt;code&gt;localStorage&lt;/code&gt; is a bad place to keep it the moment you want to &lt;em&gt;ask it a
question&lt;/em&gt;: it is a string key-value store, and you end up hand-rolling a query engine over
JSON blobs.&lt;/p&gt;
&lt;p&gt;So the app runs &lt;strong&gt;SQLite compiled to WebAssembly&lt;/strong&gt;, persisted into the browser through
IndexedDB. Real SQL, real tables, real queries — the database just happens to live in the
tab instead of on a host. The data never leaves the device, which means there is nothing
to breach, nothing to migrate, and nothing to back up. For a single-user tool, the
user&apos;s own browser is a perfectly good place for their data to live.&lt;/p&gt;
&lt;h2&gt;External data: pulled, not hosted&lt;/h2&gt;
&lt;p&gt;Item names, icons, and base stats are not mine to own — they are game data with canonical
public sources. Instead of scraping and hosting them, the app fetches from public
game-data APIs on demand and lets Cloudflare&apos;s edge cache the responses. No asset
pipeline, no scraper to babysit, no copy of someone else&apos;s data drifting out of date. The
client stays thin over sources that are already authoritative.&lt;/p&gt;
&lt;h2&gt;Analytics: first-party, still serverless&lt;/h2&gt;
&lt;p&gt;The one thing people assume forces a backend is &lt;em&gt;knowing whether anyone uses your thing.&lt;/em&gt;
It does not. The app sends product events to PostHog — but through a first-party
&lt;code&gt;/ingest&lt;/code&gt; path on its own domain rather than straight to PostHog&apos;s servers. That rewrite
keeps the telemetry first-party and lets it survive ad blockers, while the actual event
storage is PostHog&apos;s problem, not mine. I run no analytics infrastructure and still get a
real funnel: it is how I know the tool holds ~20 daily actives and which features earn
their place.&lt;/p&gt;
&lt;h2&gt;What runs it&lt;/h2&gt;
&lt;p&gt;A static React/Vite bundle on &lt;strong&gt;Cloudflare Pages&lt;/strong&gt;, deployed on push, on a subdomain of a
domain I already manage. SQLite-WASM for the data layer, PostHog for the feed, public APIs
for game data. It is a PWA, so once the data is cached it works offline — which follows
naturally from the logic and storage already being local.&lt;/p&gt;
&lt;p&gt;Total cost to keep running: zero. Total servers to operate: zero.&lt;/p&gt;
&lt;h2&gt;The point&lt;/h2&gt;
&lt;p&gt;The reflex, when you want persistence and analytics, is to reach for a backend. Most of
the time you are reaching for it out of habit. Between WebAssembly builds of real
databases, edge caching in front of public data, and analytics vendors that only need a
beacon, a surprising amount of &quot;backend&quot; is just a client-side concern wearing a server
costume.&lt;/p&gt;
&lt;p&gt;Scoping the tool to twenty people is what made this possible — and, honestly, what made
it &lt;em&gt;finished&lt;/em&gt;. The serverless architecture was not a clever trick bolted on afterward; it
was the direct consequence of building the right size of thing for the actual need. The
full write-up, including the MapleRoyals scrolling problem it solves, is
&lt;a href=&quot;https://itaywol.com/projects/scrolls-assistant/&quot;&gt;on the project page&lt;/a&gt;.&lt;/p&gt;
</content:encoded><category>architecture</category><category>serverless</category><category>cloudflare</category><category>sqlite</category><category>webassembly</category></item><item><title>Author once, run in every agent: how we share AI skills at Echo</title><link>https://itaywol.com/blog/sharing-ai-skills-across-a-team/</link><guid isPermaLink="true">https://itaywol.com/blog/sharing-ai-skills-across-a-team/</guid><description>At Echo we use Claude Code, Cursor, and Codex side by side. adeptability lets us author a coding skill or subagent once and render it into all three — instead of copying the same instruction into three formats and watching them drift.</description><pubDate>Sat, 04 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At &lt;a href=&quot;https://echo.ai/&quot;&gt;Echo&lt;/a&gt; we lean on AI coding agents hard, and we do not standardize on
one. I use &lt;a href=&quot;https://claude.com/claude-code&quot;&gt;Claude Code&lt;/a&gt;, someone else lives in Cursor,
another engineer runs Codex. That is deliberate — the same &quot;engineers as product owners&quot;
instinct that shapes how we build also means people pick the tool that fits their head.&lt;/p&gt;
&lt;p&gt;The problem is that a &lt;em&gt;coding skill&lt;/em&gt; — how we run the tests, what the PR-review checklist
is, which boring-stack conventions we hold to — is the same regardless of which agent you
opened. But the moment you write it down, every harness wants it in its own format.
Claude Code wants &lt;code&gt;SKILL.md&lt;/code&gt; files under &lt;code&gt;.claude/skills/&lt;/code&gt;. Cursor wants &lt;code&gt;.mdc&lt;/code&gt; rules with
its own frontmatter. Codex reads one aggregated &lt;code&gt;AGENTS.md&lt;/code&gt; with a size budget. Write the
convention three times, and within a week the three copies have drifted and nobody trusts
any of them.&lt;/p&gt;
&lt;p&gt;I built &lt;a href=&quot;https://itaywol.com/projects/adeptability/&quot;&gt;adeptability&lt;/a&gt; to kill that drift. This is how it works
and how we actually use it.&lt;/p&gt;
&lt;h2&gt;The idea: skill as source, harness as build target&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;adept&lt;/code&gt; treats one canonical skill as the source of truth and every harness&apos;s format as a
build target — the same relationship your source code has to compiled artifacts.&lt;/p&gt;
&lt;p&gt;You author once: a &lt;code&gt;SKILL.md&lt;/code&gt; with YAML frontmatter and a markdown body, plus optional
&lt;code&gt;scripts/&lt;/code&gt; and &lt;code&gt;references/&lt;/code&gt; sidecars. Then:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;adept sync     # render the canonical skill into every enabled harness
adept status   # init state, libraries, harnesses, and drift at a glance
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;adept sync&lt;/code&gt; writes &lt;code&gt;.claude/skills/run-tests/SKILL.md&lt;/code&gt;, &lt;code&gt;.cursor/rules/run-tests.mdc&lt;/code&gt;,
the right block in Codex&apos;s &lt;code&gt;AGENTS.md&lt;/code&gt;, and so on — each in that harness&apos;s native format,
with the frontmatter translation, activation rules, and size budgets handled per harness.
Subagents work the same way: one canonical agent definition renders into
&lt;code&gt;.claude/agents/&lt;/code&gt;, Cursor&apos;s agents, Codex&apos;s, and the rest.&lt;/p&gt;
&lt;h2&gt;Edits happen inside harnesses, so sync goes both ways&lt;/h2&gt;
&lt;p&gt;The naive version of this tool is one-directional and breaks the first time someone tweaks
a rule inside Cursor. &lt;code&gt;adept&lt;/code&gt; handles it: a content-hash state machine classifies every
rendered file as &lt;code&gt;synced&lt;/code&gt;, &lt;code&gt;ahead&lt;/code&gt;, &lt;code&gt;behind&lt;/code&gt;, or &lt;code&gt;diverged&lt;/code&gt;. Edit a skill inside a harness
and &lt;code&gt;adept sync-from&lt;/code&gt; pulls that edit back into the canonical source, then &lt;code&gt;adept sync&lt;/code&gt;
republishes it everywhere. There is no lockfile — drift is computed from hashes at read
time, so there is no second artifact to keep honest.&lt;/p&gt;
&lt;h2&gt;How we share at Echo&lt;/h2&gt;
&lt;p&gt;Two layers, because we have two kinds of skills.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Org-wide conventions live in a versioned library.&lt;/strong&gt; The stuff every repo should know —
our code-as-liability conventions, the boring-stack choices, a shared PR-reviewer subagent
that reviews to our standard — lives in a library with a remote and a ref. A project
commits only the &lt;em&gt;reference&lt;/em&gt;, not the content; &lt;code&gt;adept&lt;/code&gt; materializes the skills into a
per-machine store on demand. A teammate clones a repo, runs &lt;code&gt;adept&lt;/code&gt;, and gets the same
skills and the same reviewer agent rendered into whichever harness they use. Bump the
library ref and everyone moves together.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Project-specific skills are committed in the repo.&lt;/strong&gt; How &lt;em&gt;this&lt;/em&gt; service runs its tests,
builds, and deploys is not org-wide knowledge — it belongs to the repo. Those canonical
skills sit in the project, and &lt;code&gt;adept sync&lt;/code&gt; renders them locally. Clone, sync, and your
agent already knows how to run the test suite and ship the thing, no matter which agent it
is.&lt;/p&gt;
&lt;p&gt;The payoff is concrete: a convention is written once and true everywhere. When we change
how we review PRs, I edit one skill and bump the library — I am not chasing the same
change through three formats and hoping the Cursor copy and the Codex copy agree.&lt;/p&gt;
&lt;h2&gt;Scheduled work, too&lt;/h2&gt;
&lt;p&gt;Some of what we want agents to do is not interactive. &lt;code&gt;adept loop add&lt;/code&gt; composes a
discovery skill, an evaluator agent, and a cron skeleton in one shot — a scheduled loop
that goes looking for something and acts on it. It is the same author-once model applied to
automation instead of an interactive session.&lt;/p&gt;
&lt;h2&gt;Why I think this is the right shape&lt;/h2&gt;
&lt;p&gt;Echo&apos;s whole engineering bet is that code is a liability and the &lt;em&gt;solution&lt;/em&gt; is the asset.
Skills are the same. The convention — the judgment about how we work — is the asset worth
keeping. Which harness&apos;s file format it happens to be trapped in is pure liability, and
duplicating it across three of them is liability times three. &lt;code&gt;adept&lt;/code&gt; makes the skill the
durable thing and the harness format disposable, which is exactly the right way round.&lt;/p&gt;
&lt;p&gt;adeptability is a single static Go binary, installable via Homebrew, &lt;code&gt;go install&lt;/code&gt;, Nix, or
a curl script. The &lt;a href=&quot;https://itaywol.com/projects/adeptability/&quot;&gt;project page&lt;/a&gt; has the design decisions, the
&lt;a href=&quot;https://github.com/itaywol/adeptability&quot;&gt;repo&lt;/a&gt; has the code, and the
&lt;a href=&quot;https://itaywol.github.io/adeptability&quot;&gt;docs&lt;/a&gt; cover every command and a harness-by-harness
breakdown of exactly what gets emitted where.&lt;/p&gt;
</content:encoded><category>ai</category><category>developer-experience</category><category>tooling</category><category>go</category></item><item><title>Typescript Unions and Discriminating Unions</title><link>https://itaywol.com/blog/typescript-unions-and-discriminating-unions/</link><guid isPermaLink="true">https://itaywol.com/blog/typescript-unions-and-discriminating-unions/</guid><description>How to model application state with TypeScript union types, and why a literal discriminant field makes narrowing them safe.</description><pubDate>Tue, 08 Dec 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I really really like typescript .... really.&lt;/p&gt;
&lt;p&gt;A subject I would like to write about today is &lt;code&gt;Union&lt;/code&gt; types.
And how to discriminate Unions with literal type, it&apos;s a technique I use a lot in my work when working on possible states of the application (Backend/Frontend).&lt;/p&gt;
&lt;p&gt;So what is &lt;a href=&quot;https://en.wikipedia.org/wiki/Union_(set_theory)&quot;&gt;Union&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;As the set theory claims Union is the combination of sets.&lt;/p&gt;
&lt;p&gt;Our set is typescript definitions !!!&lt;/p&gt;
&lt;p&gt;Take user loading state as example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;interface UserLoadFailed{
    statusCode: number
    message: string
}
interface UserLoading{
   isLoading: boolean
   statusCode: number
}
interface UserLoaded{
   data: User
   statusCode: number
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So when declaring a function to get the current user state:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function getUserState(): UserLoadFailed | UserLoading | User;

const user = getUserState();

console.log(user.statusCode) // is ok because in all of our &quot;sets&quot; / types we have common field named statusCode

console.log(user.data)
/* we will get an error as we are referencing
the Union but `data` exists only in one &quot;set&quot;/Type in the union,
the only fields we can reach are the ones that intersecting (shared across all union types)*/
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Discriminating with literal typed field for the problem 💪&lt;/h2&gt;
&lt;p&gt;Changing our interfaces a bit and creating a &lt;code&gt;Union&lt;/code&gt; Type&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;interface UserLoadFailed {
    status: &quot;Failed&quot;;
    statusCode: number;
    message: string;
}

interface UserLoading {
   status: &quot;Loading&quot;;
   isLoading: boolean;
}

interface UserLoaded {
    status: &quot;Loaded&quot;;
    data: User;
}

type UserStateUnion = UserLoadFailed | UserLoading | UserLoaded;

function getUserState(): UserStateUnion;

const user = getUserState()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now you are union type-safe 😇&lt;/p&gt;
&lt;p&gt;If you want to discover more about Unions go ahead and open the handbook &lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html&quot;&gt;typescript handbook&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>typescript</category></item><item><title>Dockerizing NestJS application and Debugging</title><link>https://itaywol.com/blog/dockerizing-nestjs-application-and-debugging/</link><guid isPermaLink="true">https://itaywol.com/blog/dockerizing-nestjs-application-and-debugging/</guid><description>A step-by-step guide to dockerizing a NestJS app with docker-compose and attaching the VS Code debugger to the running container.</description><pubDate>Tue, 24 Nov 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A Long journey involved with Dockerizing a NestJS app and debug efficiently.&lt;/p&gt;
&lt;p&gt;This will be a &quot;step-by-step&quot; and a little explaining so you don&apos;t just copy-paste 😃.&lt;/p&gt;
&lt;h2&gt;Installation 🔨&lt;/h2&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nestjs.com/&quot;&gt;NestJS&lt;/a&gt; - a NodeJS framework for server API mainly aims to be developed with &lt;strong&gt;TypeScript&lt;/strong&gt; (but there&apos;s also a JS version).&lt;/p&gt;
&lt;p&gt;Installing NestJS convenience Command Line Interface to bootstrap the project.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install -g @nestjs/cli
// OR
yarn global add @nestjs/cli
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.docker.com/get-started&quot;&gt;Docker&lt;/a&gt; and &lt;a href=&quot;https://docs.docker.com/compose/install/&quot;&gt;Docker-Compose&lt;/a&gt; - Dockerizing/Packaging into a standardized unit for development, like Virtual Machines but much lighter and efficient, you can &lt;a href=&quot;https://www.docker.com/why-docker&quot;&gt;read more&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Docker is really awesome, I raise the entire deployment of multi micro-services
architecture in a couple of seconds using &lt;code&gt;docker-compose&lt;/code&gt; a wrapper CLI around
&lt;code&gt;docker-engine&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;IDE - I will be using &lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;Visual Studio Code&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I will be using Visual Studio Code reference mostly in the Debugging part.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Project set up ⚒&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;Now with all of these awesome tools in our hands let&apos;s set up everything 💪!&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Starting with NestJS -&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nest --help
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pretty self-explanatory and just to get familiar with the tools this CLI grants us.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;So we can create a new app -&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nest new &amp;lt;the name of your app&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nest will bootstrap the project you will be prompted to select your favorite package
management CLI.&lt;/p&gt;
&lt;p&gt;Your project folder will contain the following files/folders
&lt;img src=&quot;https://itaywol.com/images/blog/dockerizing-nestjs-application-and-debugging/nest-project-structure.png&quot; alt=&quot;NestJS project folder structure&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Let&apos;s move to &lt;code&gt;Dockerfile&lt;/code&gt; and &lt;code&gt;.dockerignore&lt;/code&gt; files -&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;touch Dockerfile .dockerignore
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Dockerfile&lt;/code&gt; - we describe a set of instructions that eventually will build our Docker Image.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.dockerignore&lt;/code&gt; - we describe a set of file/folder paths to be ignored and wouldn&apos;t copy into the build context of the Dockerfile.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Writing our &lt;code&gt;Dockerfile&lt;/code&gt; and &lt;code&gt;.dockerignore&lt;/code&gt; -&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Dockerfile
FROM node:lts

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY . .

CMD [&quot;npm&quot;,&quot;run&quot;,&quot;start&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Breaking things up -&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;FROM node:lts&lt;/code&gt; - &lt;code&gt;FROM&lt;/code&gt; tells our build context which image are we basing/extending on in our build context, &lt;code&gt;node:lts&lt;/code&gt; i choose an &lt;a href=&quot;https://hub.docker.com/_/node&quot;&gt;official image of NodeJS&lt;/a&gt; so basically it is &lt;code&gt;IMAGE:TAG&lt;/code&gt; where &lt;code&gt;TAG&lt;/code&gt; stands for version typically aligned with &lt;a href=&quot;https://semver.org/&quot;&gt;SemVer (Semantic Versioning)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So &lt;code&gt;node:lts&lt;/code&gt; is NodeJS precompiled Docker image with Npm &amp;amp; Yarn CLI installed (You can view their Dockerfile &lt;a href=&quot;https://github.com/nodejs/docker-node/blob/ba99d6d8dfa58fa4595ad3b23693d17fad05c44e/14/stretch/Dockerfile&quot;&gt;here&lt;/a&gt;).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;IF&lt;/strong&gt; I was to create a &lt;strong&gt;Java&lt;/strong&gt; project I would have probably use an &lt;a href=&quot;https://hub.docker.com/_/openjdk&quot;&gt;OpenJDK image&lt;/a&gt; to compile my Java project.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;WORKDIR&lt;/code&gt; - short term for the working directory, we define in which directory are we going to work on &lt;em&gt;INSIDE THE BUILD CONTEXT AND THE FUTURE CREATED IMAGE&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Browsing the Internet you will see diverse answers around where the &lt;code&gt;WORKDIR&lt;/code&gt; should be, eventually, it is your preference!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I use &lt;code&gt;/app&lt;/code&gt; most of the time.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;NOTE&lt;/em&gt; - it is best practice to not move through directories outside of the WORKDIR in the further instructions given to the Dockerfile (build context...)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;COPY package*.json ./&lt;/code&gt; - &lt;code&gt;COPY&lt;/code&gt; would copy files from side to side, the left side is the host computer (where you ran docker build if haven&apos;t run it from a different folder using the -f flag), so I tell the build context &lt;code&gt;COPY&lt;/code&gt; my &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;package-lock.json&lt;/code&gt; to &lt;code&gt;./&lt;/code&gt; so if you are a bit familiar with &quot;FileSystems&quot; &lt;code&gt;.&lt;/code&gt; stands for the current directory and I add the &lt;code&gt;/&lt;/code&gt; just to be sure that I copy multiple files into the current folder.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;RUN npm install&lt;/code&gt; - &lt;code&gt;RUN&lt;/code&gt; executes the command as you do in your Console (be aware we are always in the &lt;code&gt;WORKDIR&lt;/code&gt; we specified).&lt;/p&gt;
&lt;p&gt;So I just ran &lt;code&gt;npm install&lt;/code&gt; to install all dependencies/devDependencies inside the build context.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;COPY . .&lt;/code&gt; - after installed everything successfully (hopefully) we copy the entire project directory into our working directory.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I usually copy just the package*.json and install before copying everything so if something fails with &lt;code&gt;npm install&lt;/code&gt; it will break the build before I try to copy everything else.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;CMD [&quot;npm&quot;,&quot;run&quot;,&quot;start&quot;]&lt;/code&gt; - the command that will be triggered when we raise the image to a running container.&lt;/p&gt;
&lt;p&gt;So I will be using a script as specified in the &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note for docker images of CLI you better use &lt;code&gt;ENTRYPOINT&lt;/code&gt; you can read more &lt;a href=&quot;https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#entrypoint&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That&apos;s it for now with the Dockerfile.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# .dockerignore
dist
node_modules
*.log
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I Specify which file/folders I wouldn&apos;t like to be copied into my image.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;node_modules&lt;/code&gt; - I would like the node_modules to be installed from the image as all of the modules which require binaries of Linux like (&lt;a href=&quot;https://en.wikipedia.org/wiki/GNU_Compiler_Collection&quot;&gt;gcc&lt;/a&gt;) will be compiled using the binaries that are pre-compiled into the &lt;code&gt;node:lts&lt;/code&gt; image.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dist&lt;/code&gt; - Later when we modify the Dockerfile for build/production purposes I wouldn&apos;t like that in any case my host &lt;code&gt;dist&lt;/code&gt; folder will be copied and affect my build output.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;*.log&lt;/code&gt; - I just don&apos;t like log files :).&lt;/p&gt;
&lt;h2&gt;Building the image and running it 🐳&lt;/h2&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Build -&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker build -t &amp;lt;your image name&amp;gt;:1.0.0 .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We initiate a docker command to build an image with a specified tag (-t) at the &lt;code&gt;.&lt;/code&gt; current directory (where our project is and the docker files).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This can take a couple of seconds/minutes depending on your project dependencies and Ethernet speed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Running the ready image -&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run &amp;lt;you image name&amp;gt;:1.0.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your Console &lt;a href=&quot;https://en.wikipedia.org/wiki/Standard_streams&quot;&gt;STDIN&lt;/a&gt; will be directed into the newly created container of the image you built.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can &lt;code&gt;CTRL+C&lt;/code&gt; to get out but it will shut your container down.&lt;/p&gt;
&lt;p&gt;To keep the container running you can run it detached using the &lt;code&gt;-d&lt;/code&gt; flag when using the &lt;code&gt;docker run&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If running in detached you can initiate commands on the running container using the &lt;code&gt;docker exec&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;If running in detached in order to stop the container you can use &lt;code&gt;docker container stop &amp;lt; name of the *container* &amp;gt;/&amp;lt; hash of the *container* &amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Docker-Compose orchestrate Docker infrastructure 🐳&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;A bit of overkill for a single service but for 2 and above this is just fantastic.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;touch docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We create a &lt;code&gt;docker-compose.yml&lt;/code&gt; file (default file name by the CLI of docker-compose)&lt;/p&gt;
&lt;p&gt;⚠ docker-compose.yml is a bit long to explain so i documented on the code itself and you can read more &lt;a href=&quot;https://docs.docker.com/compose/compose-file/&quot;&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;version: &quot;3.8&quot; # Specify the version of docker-compose we will use

services: # Specify our services
  backend: # define our first service
    build: # define the build context
      dockerfile: ./Dockerfile # the docker file to use by default it looks for Dockerfile in the context dir so we can omit this key :)
      context: . # where is the build context folder
    image: custom_image_name:1.0.0 # in case we are building the built image name will be from the image entry
    environment:
      NODE_ENV: development
      PORT: 3000
    ports:
      - 3000:3000 # &amp;lt;host port&amp;gt; : &amp;lt;container port&amp;gt;
      - 9229:9229 # 9229 is the default node debug port
    volumes:
        - &quot;/app/node_modules&quot; # save the compiled node_modules to anonymous volume so make sure we don&apos;t attach the volume to our host node_modules
        - &quot;./:/app&quot; # link our project directory to the docker  directory so any change will get updated in the running container and also we will benefit from sourcemaps for debugging
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So now when we have in our hands the docker-compose file ready just:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker-compose up -d

# &apos;-d&apos; - for detached
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Docker-compose on the first time will build the image and raise it entirely as specified in the &lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Why compose? for our first service without using docker-compose inorder to raise it as specified we had to &lt;strong&gt;build&lt;/strong&gt; it and then &lt;strong&gt;run&lt;/strong&gt; it as so: &lt;code&gt;docker run -d -p &apos;3000:3000&apos; -p &apos;9229:9229&apos; -e &apos;NODE_ENV=development&apos; -e &apos;PORT=3000&apos; -v &apos;${PWD}:/app&apos; -v &apos;/app/node_modules&apos;&lt;/code&gt; and thats for one service imagine your self raising microservice architecture of 8+ services.... INSANITY 😵.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Debugging 🐛&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;package.json&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// change start:debug script
&quot;start:debug&quot;: &quot;nest start --debug 0.0.0.0:9229 --watch&quot;,
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By using 0.0.0.0 address inside the docker we allow access from the external network (our host) into the debugger in the container.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Docker-compose.yml&lt;/strong&gt; we must override the default command in order to start with the debug command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;version: &quot;3.8&quot;

services:
  backend:
    build:
      dockerfile: ./Dockerfile
      context: .
    image: custom_image_name:1.0.0
    environment:
      NODE_ENV: development
      PORT: 3000
    ports:
      - 3000:3000
      - 9229:9229
    volumes:
        - &quot;/app/node_modules&quot;
        - &quot;./:/app&quot;
    command: npm run start:debug # override entry command
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;VSCode:&lt;/strong&gt;
&lt;img src=&quot;https://itaywol.com/images/blog/dockerizing-nestjs-application-and-debugging/vscode-create-launch-json.png&quot; alt=&quot;VS Code Run panel with the create a launch.json file link&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It will create a &lt;code&gt;launch.json&lt;/code&gt; file where you specify the debug configurations.&lt;/p&gt;
&lt;p&gt;And adjust it as so:
&lt;img src=&quot;https://itaywol.com/images/blog/dockerizing-nestjs-application-and-debugging/vscode-launch-json-config.png&quot; alt=&quot;launch.json with a node attach configuration: address 127.0.0.1, port 9229, sourceMaps and restart enabled, localRoot and remoteRoot mapped&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Breaking things up:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;type&lt;/code&gt; - the type of debugger in our case is &lt;code&gt;node&lt;/code&gt; debugger.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;request&lt;/code&gt; - we use &lt;code&gt;attach&lt;/code&gt; to attach into running debug instance, &lt;code&gt;launch&lt;/code&gt; is to raise a debug instance and attach to it.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;name&lt;/code&gt; - a name given to the debug configuration.
&lt;img src=&quot;https://itaywol.com/images/blog/dockerizing-nestjs-application-and-debugging/vscode-debug-toolbar.png&quot; alt=&quot;VS Code debug toolbar showing the named attach configuration&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;protocol&lt;/code&gt; - the NodeJS debugger protocol to use (you can read more &lt;a href=&quot;https://nodejs.org/en/docs/guides/debugging-getting-started/&quot;&gt;here&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;address&lt;/code&gt; - the remote address to look out for the debugging instance by default when we raise with docker-compose it uses a network driver that uses the host namespace ( you can read more about host network driver &lt;a href=&quot;https://docs.docker.com/network/host/&quot;&gt;here&lt;/a&gt; )&lt;/p&gt;
&lt;p&gt;&lt;code&gt;port&lt;/code&gt; - the debugging port to attach to (we use the NodeJS default port 9229)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sourceMaps&lt;/code&gt; - our project is bootstrapped with NestJs by default it ships with typescript so when typescript compiles it by default ships with source maps so when we place breakpoints in our typescript it translates the breakpoint location to the compiled version (JavaScript).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;restart&lt;/code&gt; - we set &lt;code&gt;true&lt;/code&gt; so the debugger will try to reattach upon disconnection&lt;/p&gt;
&lt;p&gt;&lt;code&gt;localRoot&lt;/code&gt; - where are the local files in the host machine.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;remoteRoot&lt;/code&gt; - where are the files in the remote (the running container).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Most of the disconnections are caused by Hot Reloading the code due to code change.&lt;/p&gt;
&lt;p&gt;If you bootstrap a project of your own(and not from NestJS) make sure &lt;a href=&quot;https://www.typescriptlang.org/tsconfig#sourceMap&quot;&gt;source maps&lt;/a&gt; is enabled in &lt;code&gt;tsconfig.json&lt;/code&gt; in order to enable debugging.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And you are welcome&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://itaywol.com/images/blog/dockerizing-nestjs-application-and-debugging/vscode-breakpoint-hit.png&quot; alt=&quot;VS Code hitting a breakpoint in the NestJS app running inside the container&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/itaywol/nestjs-docker-debugging-tutorial&quot;&gt;Link to the final project&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>docker</category><category>nestjs</category><category>debugging</category></item></channel></rss>