{"id":321324,"date":"2020-09-24T07:45:29","date_gmt":"2020-09-24T14:45:29","guid":{"rendered":"https:\/\/css-tricks.com\/?p=321324"},"modified":"2021-04-13T07:17:15","modified_gmt":"2021-04-13T14:17:15","slug":"a-gentle-introduction-to-using-a-docker-container-as-a-dev-environment","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/a-gentle-introduction-to-using-a-docker-container-as-a-dev-environment\/","title":{"rendered":"A Gentle Introduction to Using a Docker Container as a Dev Environment"},"content":{"rendered":"\n

Sarcasm disclaimer:<\/strong> This article is mostly sarcasm. I do not think that I actually speak for Dylan Thomas and I would never encourage you to foist a light theme on people who don\u2019t want it. No matter how wrong they may be.<\/p>\n\n\n\n

When Dylan Thomas penned the words<\/a>, “Do not go gentle into that good night,” he was talking about death. But if he were alive today, he might be talking about Linux containers. There is no way to know for sure because he passed away in 1953, but this is the internet, so I feel extremely confident speaking authoritatively on his behalf.<\/p>\n\n\n\n\n\n\n\n

My confidence comes from a complete overestimation of my skills and intelligence coupled with the fact that I recently tried to configure a Docker container as my development environment. And I found myself raging against the dying of the light as Docker rejected every single attempt I made like I was me and it was King James screaming, “NOT IN MY HOUSE!”<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

Pain is an excellent teacher. And because I care about you and have no other ulterior motives, I want to use that experience to give you a “gentle” introduction to using a Docker container as a development environment. But first, let’s talk about whyyyyyyyyyyy<\/em> you would ever want to do that.<\/p>\n\n\n

kbutwhytho?<\/h3>\n\n\n

Close your eyes and picture this: a grown man dressed up like a fox.<\/p>\n\n\n\n

Wait. No. Wrong scenario.<\/p>\n\n\n\n

Instead, picture a project that contains not just your source code, but your entire development environment and all the dependencies and runtimes your app needs. You could then give that project to anyone anywhere (like the fox guy) and they could run your project without having to make a lick of configuration changes to their own environment.<\/p>\n\n\n\n

This is exactly what Docker containers do. A Dockerfile defines an entire runtime environment with a single file. All you would need is a way to develop inside of that container.<\/p>\n\n\n\n

Wait for it…<\/p>\n\n\n

VS Code and Remote – Containers<\/h3>\n\n\n

VS Code has an extension called Remote – Containers<\/a> that lets you load a project inside a Docker container and connect to it with VS Code. That’s some Inception-<\/em>level stuff right there. (Did he make it out?! THE TALISMAN NEVER ACTUALLY STOPS SPINNING.) It’s easier to understand if we (and by \u201cwe\u201d I mean you<\/em>) look at it in action.<\/p>\n\n\n

Adding a container to a project<\/h3>\n\n\n

Let’s say for a moment that you are on a high-end gaming PC that you built for your kids and then decided to keep if for yourself. I mean, why exactly do they<\/em> deserve a new computer again? Oh, that’s right. They don’t. They can’t even take out the trash on Sundays even though you TELL THEM EVERY WEEK.<\/p>\n\n\n\n

This is a fresh Windows machine with WSL2 and Docker installed, but that’s all. Were you to try and run a Node.js project on this machine, Powershell would tell you that it has absolutely no idea what you are referring to and maybe you mispelled something. Which, in all fairness, you do suck at spelling. Remember that time in 4\u1d57\u02b0 grade when you got knocked out of the first round of the spelling bee because you couldn’t spell “fried.” FRYED? There’s no “Y” in there!<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

Now this is not a huge problem \u2014 you could always skip off and install Node.js. But let’s say for a second that you can’t be bothered to do that and you’re pretty sure that skipping is not something adults do.<\/p>\n\n\n\n

Instead, we can configure this project to run in a container that already has Node.js installed. Now, as I’ve already discussed, I have no idea how to use Docker. I can barely use the microwave. Fortunately, VS Code will configure your project for you \u2014 to an extent.<\/p>\n\n\n\n

From the Command Palette, there is an “Add Development Container Configuration Files…” command. This command looks at your project and tries to add the proper container definition.<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

In this case, VS Code knows I’ve got a Node project here, so I’ll just pick Node.js 14. Yes, I am aware that 12 is LTS right now, but it’s gonna be 14 in [checks watch] one month and I’m an early adopter, as is evidenced by my interest in container technology just now in 2020.<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

This will add a .devcontainer<\/code> folder with some assets inside. One is a Dockerfile<\/code> that contains the Node.js image that we’re going to use, and the other is a devcontainer.json<\/code> that has some project level configuration going on.<\/p>\n\n\n\n

Now, before we touch anything and break it all (we’ll get to that, trust me), we can select “Rebuild and Reopen in Container” from the Command Palette. This will restart VS Code and set about building the container. Once it completes (which can take a while the first time if you’re not on a high-end gaming PC that your kids will never know the joys of), the project will open inside of the container. VS Code is connected to the container, and you know that because it says so in the lower left-hand corner.<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

Now if we open the terminal in VS Code, Powershell is conspicously absent because we are not on Windows anymore, Dorthy. We are now in a Linux container. And we can both npm install<\/code> and npm start<\/code> in this magical land.<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

This is an Express App, so it should be running on port 3000. But if you try and visit that port, it won’t load. This is because we need to map a port in the container to 3000 on our localhost. As one does.<\/p>\n\n\n\n

Fortunately, there is a UI for this.<\/p>\n\n\n\n

The Remote Containers extension puts a “Remote Explorer” icon in the Action Bar. Which is on the left-hand side for you, but the right-hand side for me. Because I moved it and you should too<\/a>.<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

There are three sections here, but look at the bottom one which says “Port Forwarding,” I’m not the sandwich with the most lettuce, but I’m pretty sure that’s what we want here. You can click on the “Forward a Port” and type “3000,” Now if we try and hit the app from the browser…<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

Mostly things, “just worked.” But the configuration is also quite simple. Let’s look at how we can start to customize this setup by automating some of the aspects of the project itself. Project specific configuration is done in the devcontainer.json<\/code> file.<\/p>\n\n\n

Automating project configuration<\/h3>\n\n\n

First off, we can automate the port forwarding by adding a forwardPorts<\/code> variable and specifying 3000<\/code> as the value. We can also automate the npm install<\/code> command by specifying the postCreateCommand<\/code> property. And let’s face it, we could all stand to run AT LEAST one less npm install<\/code>.<\/p>\n\n\n\n

{\n\u00a0 \/\/ ...\n\u00a0 \/\/ Use 'forwardPorts' to make a list of ports inside the container available locally.\n\u00a0 \"forwardPorts\": [3000],\n\u00a0 \/\/ Use 'postCreateCommand' to run commands after the container is created.\n\u00a0 \"postCreateCommand\": \"npm install\",\n\u00a0 \/\/ ...\n}<\/code><\/pre>\n\n\n\n

Additionally, we can include VS Code extensions. The VS Code that runs in the Docker container does not automatically get every extension you have installed. You have to install them in the container, or just include them like we’re doing here.<\/p>\n\n\n\n

Extensions like Prettier and ESLint are perfect for this kind of scenario. We can also take this opportunity to foist a light theme on everyone because it turns out that dark themes are worse for reading and comprehension<\/a>. I feel like a prophet<\/a>.<\/p>\n\n\n\n

\/\/ For format details, see https:\/\/aka.ms\/vscode-remote\/devcontainer.json or this file's README at:\n\/\/ https:\/\/github.com\/microsoft\/vscode-dev-containers\/tree\/v0.128.0\/containers\/javascript-node-14\n{\n\u00a0 \/\/ ...\n\u00a0 \/\/ Add the IDs of extensions you want installed when the container is created.\n\u00a0 \"extensions\": [\n\u00a0 \u00a0 \"dbaeumer.vscode-eslint\",\n\u00a0 \u00a0 \"esbenp.prettier-vscode\",\n\u00a0 \u00a0 \"GitHub.github-vscode-theme\"\n\u00a0 ]\n\u00a0 \/\/ ...\n}<\/code><\/pre>\n\n\n\n

If you’re wondering where to find those extension ID’s, they come up in intellisense (Ctrl<\/code>\/Cmd<\/code> + Shift<\/code>) if you have them installed. If not, search the extension marketplace, right-click the extension and say “Copy extension ID.” Or even better, just select “Add to devcontainer.json.”<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

By default, the Node.js container that VS Code gives you has things like git and cURL already installed. What it doesn’t have, is “cowsay,” And we can’t have a Linux environment without cowsay. That’s in the Linux bylaws (it’s not). I don’t make the rules. We need to customize this container to add that.<\/p>\n\n\n

Automating environment configuration<\/h3>\n\n\n

This is where things went off the rails for me. In order to add software to a development container, you have to edit the Dockerfile. And Linux has no tolerance for your shenanigans or mistakes.<\/p>\n\n\n\n

The base Docker container that you get with the container configurations in VS Code is Debian Linux. Debian Linux uses the apt-get dependency manager.<\/p>\n\n\n\n

apt-get install cowsay<\/code><\/pre>\n\n\n\n

We can add this to the end of the Dockerfile. Whenever you install something from apt-get, run an apt-get update<\/code> first. This command updates the list of packages and package repos so that you have the most current list cached. If you don’t do this, the container build will fail and tell you that it can’t find “cowsay.”<\/p>\n\n\n\n

# To fully customize the contents of this image, use the following Dockerfile instead:\n# https:\/\/github.com\/microsoft\/vscode-dev-containers\/tree\/v0.128.0\/containers\/javascript-node-14\/.devcontainer\/Dockerfile\nFROM mcr.microsoft.com\/vscode\/devcontainers\/javascript-node:0-14\n# ** Install additional packages **\nRUN apt-get update \\\n\u00a0 && apt-get -y install cowsay<\/code><\/pre>\n\n\n\n

A few things to note here…<\/p>\n\n\n\n

  1. That RUN<\/code> command is a Docker thing and it creates a new “layer.” Layers are how the container knows what has changed and what in the container needs to be updated when you rebuild it. They’re kind of like cake layers except that you don’t<\/em> want a lot of them because enormous cakes are awesome. Enormous containers are not. You should try and keep related logic together in the same RUN<\/code> command so that you don’t create unnecessary layers.<\/li>
  2. That \\<\/code> denotes a line break at the end of a line. You need it for multi-line commands. Leave it off and you will know the pain of many failed Docker builds.<\/li>
  3. The &&<\/code> is how you add an additional command to the RUN<\/code> line. For the love of god, don’t forget that \\<\/code> on the previous line.<\/li>
  4. The -y<\/code> flag is important because by default, apt-get is going to prompt you to ensure you really want to install what you just tried to install. This will cause the container build to fail because there is nobody there to say Y<\/code> or N<\/code>. The -y<\/code> flag is shorthand for “don’t bother me with your silly confirmation prompts”. Apparently everyone is supposed to know this already. I didn\u2019t know it until about four hours ago. <\/li><\/ol>\n\n\n\n

    Use the command prompt to select \u201cRebuild Container\u201d\u2026<\/p>\n\n\n\n

    \"\"<\/figure>\n\n\n\n

    And, just like that\u2026<\/p>\n\n\n\n

    \"\"<\/figure>\n\n\n\n

    It doesn\u2019t work.<\/p>\n\n\n\n

    This the first lesson in what I like to call, “Linux Vertigo.” There are so many distributions of Linux and they don’t all handle things the same way. It can be difficult to figure out why things work in one place (Mac, WSL2) and don’t work in others. The reason why “cowsay” isn’t available, is that Debian puts “cowsay” in \/usr\/games<\/code>, which is not included in the PATH<\/code> environment variable.<\/p>\n\n\n\n

    One solution would be to add it to the PATH<\/code> in the Dockerfile. Like this…<\/p>\n\n\n\n

    FROM mcr.microsoft.com\/vscode\/devcontainers\/javascript-node:0-14\nRUN apt-get update \\\n\u00a0 && apt-get -y install cowsay\nENV PATH=\"\/usr\/games:${PATH}\"<\/code><\/pre>\n\n\n\n
    \"\"<\/figure>\n\n\n\n

    EXCELLENT. We’re solving real problems here, folks. People like cow one-liners. I bullieve<\/em> I herd<\/em> that somewhere.<\/p>\n\n\n\n

    To summarize, project configuration (forwarding ports, installing project depedencies, etc.) is done in the devcontainer.json<\/code> and environment configuration (installing software) is done in the \u201cDockerfile.\u201d Now let\u2019s get brave and try something a little more edgy.<\/p>\n\n\n

    Advanced configuration<\/h3>\n\n\n

    Let’s say for a moment that you have a gorgeous, glammed out terminal setup that you really want to put in the container as well. I mean, just because you are developing in a container doesn\u2019t mean that your terminal has to be boring. But you also wouldn\u2019t want to reconfigure your pretentious zsh setup for every project that you open. Can we automate that too? Let\u2019s find out.<\/p>\n\n\n\n

    Fortunately, zsh is already installed in the image that you get. The only trouble is that it’s not the default shell when the container opens. There are a lot of ways that you can make zsh the default shell in a normal Docker scenario, but none of them will work here. This is because you have no control over the way the container is built.<\/p>\n\n\n\n

    Instead, look again to the trusty devcontainer.json<\/code> file. In it, there is a \"settings\"<\/code> block. In fact, there is a line already there showing you that the default terminal is set to \"\/bin\/bash\"<\/code>. Change that to \"\/bin\/zsh\"<\/code>.<\/p>\n\n\n\n

    \/\/ Set *default* container specific settings.json values on container create.\n\"settings\": {\n\u00a0 \"terminal.integrated.shell.linux\": \"\/bin\/zsh\"\n}<\/code><\/pre>\n\n\n\n

    By the way, you can set ANY VS Code setting there. Like, you know, moving the sidebar to the right-hand side. There – I fixed it for you.<\/p>\n\n\n\n

    \/\/ Set default container specific settings.json values on container create.\n\"settings\": {\n  \"terminal.integrated.shell.linux\": \"\/bin\/zsh\",\n  \"workbench.sideBar.location\": \"right\"\n},<\/code><\/pre>\n\n\n\n

    And how about those pretentious plugins that make you better than everyone else? For those you are going to need your .zshrc<\/code> file. The container already has oh-my-zsh in it, and it’s in the “root” folder. You just need to make sure you set the path to ZSH<\/code> at the top of the .zshrc<\/code> so that it points to root. Like this…<\/p>\n\n\n\n

    # Path to your oh-my-zsh installation.\nexport ZSH=\"\/root\/.oh-my-zsh\"\n\u2028\n# Set name of the theme to load --- if set to \"random\", it will\n# load a random theme each time oh-my-zsh is loaded, in which case,\n# to know which specific one was loaded, run: echo $RANDOM_THEME\n# See https:\/\/github.com\/ohmyzsh\/ohmyzsh\/wiki\/Themes\nZSH_THEME=\"cloud\"\n\u2028\n# Which plugins would you like to load?\nplugins=(zsh-autosuggestions nvm git)\n\u2028\nsource $ZSH\/oh-my-zsh.sh<\/code><\/pre>\n\n\n\n

    Then you can copy in that sexy .zshrc<\/code> file to the root folder in the Dockerfile. I put that .zshrc<\/code> file in the .devcontainer<\/code> folder in my project.<\/p>\n\n\n\n

    COPY .zshrc \/root\/.zshrc<\/code><\/pre>\n\n\n\n

    And if you need to download a plugin before you install it, do that in the Dockerfile with a RUN<\/code> command. Just remember to group all of these into one command since each RUN<\/code> is a new layer. You are nearly a container expert now. Next step is to write a blog post about it and instruct people on the ways of Docker like you invented the thing.<\/p>\n\n\n\n

    RUN git clone https:\/\/github.com\/zsh-users\/zsh-autosuggestions ${ZSH_CUSTOM:-~\/.oh-my-zsh\/custom}\/plugins\/zsh-autosuggestions<\/code><\/pre>\n\n\n\n

    Look at the beautiful terminal! Behold the colors! The git plugin which tells you the branch and adds a lightning emoji! Nothing says, “I know what I’m doing” like a customized terminal. I like to take mine to Starbucks and just let people see it in action and wonder if I’m a celebrity.<\/p>\n\n\n\n

    \"\"<\/figure>\n\n\n

    Go gently<\/h3>\n\n\n

    Hopefully you made it to this point and thought, “Geez, this guy is seriously overreacting. This is not that hard.” If so, I have successfully saved you. You are welcome. No need to thank me. Yes, I do have an Amazon wish list.<\/p>\n\n\n\n

    For more information on Remote Containers, including how to do things like add a database or use Docker Compose, check out the official Remote Container docs<\/a>, which provide much more clarity with 100% less neurotic commentary.<\/p>\n","protected":false},"excerpt":{"rendered":"

    Sarcasm disclaimer: This article is mostly sarcasm. I do not think that I actually speak for Dylan Thomas and I would never encourage you to foist a light theme on people who don\u2019t want it. No matter how wrong they may be. When Dylan Thomas penned the words, “Do not go gentle into that good […]<\/p>\n","protected":false},"author":250411,"featured_media":321341,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_bbp_topic_count":0,"_bbp_reply_count":0,"_bbp_total_topic_count":0,"_bbp_total_reply_count":0,"_bbp_voice_count":0,"_bbp_anonymous_reply_count":0,"_bbp_topic_count_hidden":0,"_bbp_reply_count_hidden":0,"_bbp_forum_subforum_count":0,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"c2c_always_allow_admin_comments":false,"footnotes":"","jetpack_publicize_message":"A Gentle Introduction to Using a Docker Container as a Dev Environment by @burkeholland","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":[]},"categories":[4],"tags":[1274,1418],"jetpack_publicize_connections":[],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2020\/09\/docker-logo.png?fit=1200%2C600&ssl=1","jetpack-related-posts":[{"id":306126,"url":"https:\/\/css-tricks.com\/an-annotated-docker-config-for-front-end-web-development\/","url_meta":{"origin":321324,"position":0},"title":"An Annotated Docker Config for Front-End Web Development","date":"April 1, 2020","format":false,"excerpt":"Andrew Welch sings the praises of using Docker containers for local dev environments: Here are the advan\u00adtages of Dock\u00ader for me: \u2022 Each appli\u00adca\u00adtion has exact\u00adly the envi\u00adron\u00adment it needs to run, includ\u00ading spe\u00adcif\u00adic ver\u00adsions of any of the plumb\u00ading need\u00aded to get it to work (PHP, MySQL, Post\u00adgres, whatever)\u2026","rel":"","context":"In "Link"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2020\/04\/docker-love-hate.png?fit=1200%2C600&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":357985,"url":"https:\/\/css-tricks.com\/dock-life-using-docker-for-all-the-things\/","url_meta":{"origin":321324,"position":1},"title":"Dock Life: Using Docker for All The Things!","date":"December 1, 2021","format":false,"excerpt":"I think if you're a DevOps person in any capacity, the utility of Docker is very clear. Your things run in containers that are identical everywhere. Assuming Docker is working\/running, the code will execute in a reliably consistent way whether that is Docker running on some developer's computer, or a\u2026","rel":"","context":"In "Link"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2020\/04\/docker-love-hate.png?fit=1200%2C600&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":254564,"url":"https:\/\/css-tricks.com\/poll-results-local-wordpress-development\/","url_meta":{"origin":321324,"position":2},"title":"Poll Results: Local WordPress Development","date":"May 10, 2017","format":false,"excerpt":"We kicked a poll off three months ago asking y'all what kind of local development environment you set up for running WordPress locally. At the time of this writing, we got 2,623 votes, so a decent amount of significance here. Especially because the question was phrased: If you're running WordPress\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":285374,"url":"https:\/\/css-tricks.com\/15-minutes-automation-nirvana\/","url_meta":{"origin":321324,"position":3},"title":"Buddy: 15 Minutes to Automation Nirvana","date":"March 26, 2019","format":false,"excerpt":"Deploying a website to the server in 2019 requires much more effort than 10 years ago. For example, here's what needs to be done nowadays to deliver a typical JS app: split the app into chunks configure webpack bundle minify .js files set up staging environment upload the files to\u2026","rel":"","context":"In "Link"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2019\/03\/photo_2019-03-25_16-36-54.jpg?fit=1200%2C600&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":253478,"url":"https:\/\/css-tricks.com\/fabrica-dev-kit\/","url_meta":{"origin":321324,"position":4},"title":"Fabrica Dev Kit","date":"April 10, 2017","format":false,"excerpt":"Fabrica Dev Kit is a toolkit for WordPress development. You... Clone a GitHub Repo Configure your WordPress project (settings, plugins, etc) through a `.yml` file Run a Ruby setup script which Downloads dev dependencies (like Gulp) through npm or yarn Builds a Docker container and volumes to run everything inside\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":340465,"url":"https:\/\/css-tricks.com\/serverless-functions-the-secret-to-ultra-productive-front-end-teams\/","url_meta":{"origin":321324,"position":5},"title":"Serverless Functions: The Secret to Ultra-Productive Front-End Teams","date":"May 31, 2021","format":false,"excerpt":"Modern apps place high demands on front-end developers. Web apps require complex functionality, and the lion's share of that work is falling to front-end devs: building modern, accessible user interfacescreating interactive elements and complex animationsmanaging complex application statemeta-programming: build scripts, transpilers, bundlers, linters, etc.reading from REST, GraphQL, and other APIsmiddle-tier\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2021\/05\/netlify-serverless-function-rest-api.jpg?fit=1200%2C600&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]}],"featured_media_src_url":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2020\/09\/docker-logo.png?fit=1024%2C512&ssl=1","_links":{"self":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/321324"}],"collection":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/users\/250411"}],"replies":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/comments?post=321324"}],"version-history":[{"count":10,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/321324\/revisions"}],"predecessor-version":[{"id":338271,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/321324\/revisions\/338271"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/media\/321341"}],"wp:attachment":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/media?parent=321324"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/categories?post=321324"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/tags?post=321324"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}