Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Local Dev With CosmosDB and devcontainers
Microsoft Azure profile imageAaron Powell
Aaron Powell forMicrosoft Azure

Posted on • Originally published ataaron-powell.com on

     

Local Dev With CosmosDB and devcontainers

When I was a consultant the nirvana that I tried to achieve on projects was to be able to clone them from source control and have everything ready to go, no wiki pages to follow on what tools to install, no unmaintained setup scripts, just clone + install dependencies. This is why I loveVS Code Remote Containers, aka devcontainers.

I’ve previously saidall projects need devcontainers, that they are anessential tool for workshops and might go overboard on it locally…

Yes, I really had 23 devcontainers on my machine. These days I don’t do any development on my machine, it all happens inside a container.

This works well for dev, I can run the web servers/APIs/etc. just fine, but there’s one piece that is more difficult… storage. Since I’m commonly using CosmosDB as the backend, I end up having a CosmosDB instance deployed to work against. While this is fine forme, if I’m creating a repo for others to use or a workshop to follow along with, there’s a hard requirement on deploying a CosmosDB service, which adds overhead to getting started.

For a while there has been aCosmosDB emulator, but it’s a Windows emulator and that still means a series of steps to install it beyond what can be in the Git repo, and I hadn’t had any luck connecting to it from a devcontainer.

Things changed this week with Microsoft Build, apreview of a Linux emulator was released. Naturally I had to take it for a spin.

Setting up the emulator

The emulator is available as a Docker image, which means it’s pretty easy to setup, just pull the image:

$> docker pull mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator
Enter fullscreen modeExit fullscreen mode

And then start a container:

$> docker run-p 8081:8081-p 10251:10251-p 10252:10252-p 10253:10253-p 10254:10254--name=cosmos-it mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator
Enter fullscreen modeExit fullscreen mode

This runs it locally, which is all well and good, but I want to use it with VS Code and devcontainers.

Cosmos devcontainers

A devcontainer is, as the name suggests, where you do your development, and since we need to development against CosmosDB it could make sense to use the emulator image as the base image and then add all the other stuff we need, like Node, dotnet, etc.

While this is a viable option, I feel like it’s probably not the simplest way. First off, you have amega container that will be running, and if you want to change anything about the dev environment, you’ll end up trashing everything, including any data you might have. Also, the emulator image is pretty slimmed down, it doesn’t have runtimes like Node or dotnet installed, so you’ll need to add the appropriate apt sources, install the runtimes, etc. Very doable, but I think that’s not the best way to tackle.

Enter Docker Compose.

I only recently learnt thatdevcontainers support Docker Compose, meaning you can create a more complex environment stack and have VS Code start it all up for you.

Let’s take theNode.js quickstart (full docs here) and run it in a devcontainer.

Our devcontainer Dockerfile

We’ll park the CosmosDB emulator for a moment and look at the Dockerfile we’ll need for this codebase.

Follow theVS Code docs to scaffold up the devcontainer definition and let’s start hacking.

Note: You may need to select “Show All Definitions” to get to the Docker Compose option, also, it’ll detect you’ve added the.devcontainer folder and prompt to open it in a container, but we’ll hold off for now until we set everything up.

The app is a Node.js app so we probably want to use that as our base image. Start by changing the base image to the Node.js image:

ARG VARIANT="16-buster"FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}
Enter fullscreen modeExit fullscreen mode

We’ll want to ensure we have theright version of Node installed, so we’ll allow the flexibility of passing that in as a container argument, but default to16 as the Node.js version.

Setting up Docker Compose

Our Dockerfile is ready for the devcontainer, and we can run it just fine, but we want it to be part of a composed environment, so it’s time to finish off the Docker Compose file.

The one that was scaffolded up for us already has what we need for the app, all that we need to do is add the CosmosDB emulator as a service.

version:"3"services:cosmos:image:mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:latestmem_limit:3gcpu_count:2environment:AZURE_COSMOS_EMULATOR_PARTITION_COUNT:10AZURE_COSMOS_EMULATOR_ENABLE_DATA_PERSISTENCE:"true"volumes:# Forwards the local Docker socket to the container.-/var/run/docker.sock:/var/run/docker-host.sockapp:# snip
Enter fullscreen modeExit fullscreen mode

We’ve added a new service calledcosmos (obvious huh!) that uses the image for the emulator and passes in the environment variables to control startup. We’ll also mount theDocker socket, just in case we need it later on.

There’s one final thing we need to configure before we open in the container, and that is to expose the CosmosDB emulator via the devcontainer port mapping. Now, it’s true we can do port mapping with the Docker Compose file, if you are running this environment via VS Code it does some hijacking of the port mapping, so we expose ports in thedevcontainer.json file, not thedocker-compose.yml file (this is more important if you’re using it with Codespaces as well, since then you don’t have access to the Docker host). But if we add the port forwarding in thedevcontainer.json it won’t know that we want to expose a port from ourcosmos service, as that’s not themain container for VS Code. Instead, we need to map the service into ourapp's network withnetwork_mode: service:cosmos:

services:cosmos:# snipapp:build:context:.dockerfile:Dockerfile.composeargs:USER_UID:1000USER_GID:1000VARIANT:16init:truevolumes:-/var/run/docker.sock:/var/run/docker-host.sock-..:/workspace:cachedentrypoint:/usr/local/share/docker-init.shcommand:sleep infinitynetwork_mode:service:cosmos
Enter fullscreen modeExit fullscreen mode

Tweaking thedevcontainer.json

Our environment is ready to go, but if you were to launch it, the devcontainer won’t start because of the following error:

[2209 ms] Start: Run in container: uname -m[2309 ms] Start: Run in container: cat /etc/passwd[2309 ms] Stdin closed![2312 ms] Shell server terminated (code: 126, signal: null)unable to find user vscode: no matching entries in passwd file
Enter fullscreen modeExit fullscreen mode

The problem here is that the base Docker image we’re using has created a user to run everything as namednode, but thedevcontainer.json file specifies theremoteUser asvscode:

//Forformatdetails,seehttps://aka.ms/devcontainer.json.Forconfigoptions,seetheREADMEat://https://github.com/microsoft/vscode-dev-containers/tree/v0.179.0/containers/docker-from-docker-compose{"name":"Docker from Docker Compose","dockerComposeFile":"docker-compose.yml","service":"app","workspaceFolder":"/workspace",//Usethisenvironmentvariableifyouneedtobindmountyourlocalsourcecodeintoanewcontainer."remoteEnv":{"LOCAL_WORKSPACE_FOLDER":"${localWorkspaceFolder}"},//Set*default*containerspecificsettings.jsonvaluesoncontainercreate."settings":{"terminal.integrated.shell.linux":"/bin/bash"},//AddtheIDsofextensionsyouwantinstalledwhenthecontaineriscreated."extensions":["ms-azuretools.vscode-docker"],//Use'forwardPorts'tomakealistofportsinsidethecontaineravailablelocally.//"forwardPorts":[],//Use'postCreateCommand'toruncommandsafterthecontaineriscreated.//"postCreateCommand":"docker --version",//Commentoutconnectasrootinstead.Moreinfo:https://aka.ms/vscode-remote/containers/non-root."remoteUser":"vscode"}
Enter fullscreen modeExit fullscreen mode

We can change theremoteUser tonode and everything is ready to go. But while we're in thedevcontainer.json file, let's add some more extensions:

"extensions":["ms-azuretools.vscode-docker","dbaeumer.vscode-eslint","esbenp.prettier-vscode","ms-azuretools.vscode-cosmosdb"],
Enter fullscreen modeExit fullscreen mode

This will give us eslint + prettier (my preferred linter and formatter), as well as the CosmosDB tools for VS Code. I also like to addnpm install as thepostCreateCommand, so all the npm packages are installed before I start to use the container.

Connecting to the CosmosDB emulator

The emulator is running in a separate container to our workspace, you can see that withdocker ps on your host:

➜ docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESa883d9a21499 azure-cosmos-db-sql-api-nodejs-getting-started_devcontainer_app "/usr/local/share/do…" 4 minutes ago Up 4 minutes azure-cosmos-db-sql-api-nodejs-getting-started_devcontainer_app_1c03a7a625470 mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:latest "/usr/local/bin/cosm…" 20 minutes ago Up 4 minutes azure-cosmos-db-sql-api-nodejs-getting-started_devcontainer_cosmos_1
Enter fullscreen modeExit fullscreen mode

So how do we address it from our app? either using its hostname or its IP address. I prefer to use the hostname, which is the name of the service in ourdocker-compose.yml file, socosmos and it’s running on port8081. For theAccount Key, we get a standard onethat you’ll find in the docs.

Openconfig.js and fill in the details:

// @ts-checkconstconfig={endpoint:"https://cosmos:8081/",key:"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",databaseId:"Tasks",containerId:"Items",partitionKey:{kind:"Hash",paths:["/category"]}};module.exports=config;
Enter fullscreen modeExit fullscreen mode

Now open the terminal and runnode app.js to run the app against the emulator.

node ➜ /workspace(main ✗)$node app.js/workspace/node_modules/node-fetch/lib/index.js:1455                        reject(new FetchError(`request to${request.url} failed, reason:${err.message}`,'system', err));                               ^FetchError: request to https://cosmos:8081/ failed, reason: self signed certificate    at ClientRequest.<anonymous>(/workspace/node_modules/node-fetch/lib/index.js:1455:11)    at ClientRequest.emit(node:events:365:28)    at TLSSocket.socketErrorListener(node:_http_client:447:9)    at TLSSocket.emit(node:events:365:28)    at emitErrorNT(node:internal/streams/destroy:193:8)    at emitErrorCloseNT(node:internal/streams/destroy:158:3)    at processTicksAndRejections(node:internal/process/task_queues:83:21){type:'system',  errno:'DEPTH_ZERO_SELF_SIGNED_CERT',  code:'DEPTH_ZERO_SELF_SIGNED_CERT',  headers:{'x-ms-throttle-retry-count': 0,'x-ms-throttle-retry-wait-time-ms': 0}}
Enter fullscreen modeExit fullscreen mode

Oh, it went 💥. That’s not what we wanted…

It turns out that we’re missing something. Node.js uses a defined list of TLS certificates, and doesn’t support self-signed certificates. The CosmosDB SDKhandles this forlocalhost, which is how the emulator isdesigned to be used, but we’re not able to access it onlocalhost (unless maybe if you named the service that in the compose file, but that’s probably a bad idea…), so we have to work around this by disabling TLS.

Note: Disabling TLS is not really a good idea, but it’s the only workaround we’ve got. Just don’t disable it on any production deployments!

Open thedevcontainer.json file, as we can use this to inject environment variables into the container when it starts up, using theremoteEnv section:

"remoteEnv":{"LOCAL_WORKSPACE_FOLDER":"${localWorkspaceFolder}","NODE_TLS_REJECT_UNAUTHORIZED":"0"},
Enter fullscreen modeExit fullscreen mode

We’ll setNODE_TLS_REJECT_UNAUTHORIZED to0, which will tell Node.js to ignore TLS errors. This will result in a warning on the terminal when the app runs, just a reminder that you shouldn’t do this in production!

Now the environment needs to be recreated, reload VS Code and it’ll detect the changes to thedevcontainer.json file and ask if you want to rebuild the environment. ClickRebuild and in a few moments your environments will be created (a lot quicker this time as the images already exist!), and you can open the terminal to run the app again:

node ➜ /workspace(main ✗)$node app.js(node:816) Warning: Setting the NODE_TLS_REJECT_UNAUTHORIZED environment variable to'0' makes TLS connections and HTTPS requests insecure by disabling certificate verification.(Use`node--trace-warnings ...` to show where the warning was created)Created database:TasksCreated container:ItemsQuerying container: ItemsCreated new item: 3 - Complete Cosmos DB Node.js Quickstart ⚡Updated item: 3 - Complete Cosmos DB Node.js Quickstart ⚡Updated isComplete totrueDeleted item withid: 3
Enter fullscreen modeExit fullscreen mode

🎉 Tada! the sample is running against the CosmosDB emulator within a Docker container, being called from another Docker container.

Conclusion

Throughout this post we’ve seen how we can create a complex environment with VS Code Remote Containers (aka, devcontainers), which uses the CosmosDB emulator to do local dev of a Node.js app against CosmosDB.

You’ll find my sampleon GitHub, should you want to spin it.

GitHub logo aaronpowell / azure-cosmos-db-sql-api-nodejs-getting-started

This sample shows you how to use the Node.js SDK to interact with Microsoft Azure Cosmos DB.

Alternative solution

After posting this article I got into a Twitter discussion in which it looks like there might be another solution to this that doesn’t require disabling TLS.Noel Bundick hasan example repo that uses theNODE_EXTRA_CA_CERTS environment variable to add thecert that comes with the emulator to Node.js at runtime, rather than disabling TLS. It’s a bit more clunky as you’ll need to run a few more steps once the devcontainer starts, but do check it out as an option.

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Invent with purpose

Any language. Any platform.

More fromMicrosoft Azure

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp