This post builds on the excellent tutorial from Tania Rascia: Create and Deploy a Node.js, Express, & PostgreSQL REST API.
I want to build a little web app that can store some data! …without installing anything new on my computer. Here’s how to configure VSCode Remote Containers to do that.
Installed on my machine:
- Docker
- VSCode
- VSCode Remote Containers Extension
- git (but I don’t talk about git operations here)
The database will be handled for you
Skip Tania’s section on setting up PostgreSQL
Skip to writing the code
Follow Tania’s section on creating an Express API.
Set up containers in VSCode
Open the directory in VSCode.
Enter the command (Ctrl-shift-P for the command prompt) “Remote Containers: Add Development Container Configuration Configuration Files.” I type “add dev” until this comes up.
Choose “Node.js 12 & PostgreSQL” for the starting point. (You may have to choose “Show all definitions…” before it gives this to you.) I search for “node postgres” and this comes up.
Now you have a .devcontainer
directory with three relevant files: docker-compose.yml
, Dockerfile
, and devcontainer.json
.
VSCode reads devcontainer.json
to figure out what to do. That references docker-compose.yml
, which references the Dockerfile
.
Set up the Postgres user
In docker-compose.yml
, two services are defined. The second one is db
, and that uses the standard postgres
Docker image. It defines a username, password, and database to create.
Change those environment variables to reference the ones in .env
as Tanya set it up:
db:
image: postgres
restart: unless-stopped
ports:
- ${DB_PORT}:${DB_PORT}
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USER}
POSTGRES_DB: ${DB_DATABASE}
When docker-compose reads this file, it reads .env
first, and makes those variables available here.
Make it easy to connect to that user
The first service in docker-compose.yml
is web
, and that’s where your app lives. It’s also where your VSCode terminal will open (as defined in devcontainer.json
when it says "service": "web"
).
I want to connect from the terminal using psql
, and I can make the connection parameters available in an environment variable, building them out of the same definitions. To the web
service definition, I add an environment
section and define CONNECTION_STRING
:
services:
web:
# ... stuff that is already there ...
environment:
CONNECTION_STRING: "postgresql://${DB_USER}:${DB_PASSWORD}@db:${DB_PORT}/${DB_DATABASE}"
This will let me run psql $CONNECTION_STRING
in the terminal to get a database prompt.
But wait! That doesn’t work yet!
Install psql in the container
The web
container doesn’t have psql
in it. We can fix that.
in docker_compose.yml
, the web
service is defined with (among other things)
build:
context: .
dockerfile: Dockerfile
This means it’s using the Dockerfile
that’s right here. Open that and add to the bottom:
# psql
RUN apt-get update && apt-get install -y postgresql-client
This fetches package definitions and then installs the package with psql
in it (but not the whole PostgreSQL database).
Initialize the schema
Whenever the container comes up, it’s time to run that init.sql
script.
Now, the script as Tania wrote it isn’t idempotent (it’ll give an error if the table exists already), so I added to the top of init.sql
:
DROP TABLE IF EXISTS books;
Do that for all the tables you create. That way you’ll start with a clean database every time.
Every time you rebuild these containers, the db
container creates itself an empty Docker volume to start with. You can have VSCode populate that after startup. Add to devcontainer.json
:
"postCreateCommand": "psql -f init.sql $CONNECTION_STRING"
Note: if you want your development data to persist (not recommended), then instead run init.sql
manually, and in docker-compose, add a named volume and mount it to /var/lib/postgresql/data in the db container.
In case you want to deploy
You’ll want the Heroku client in your container, too. Add this to the Dockerfile:
# heroku
RUN curl https://cli-assets.heroku.com/install-ubuntu.sh | sh
For authentication with Heroku, consider following Avdi’s instructions to store credentials on your own computer and mount them into the container.
Start it up!
In VSCode, run the command (Ctrl-shift-P) “Remote Containers: Rebuild and Reopen in Container”.
When the little “Starting with Dev Container” notice pops up in the bottom right, click it so you can watch it work. It runs docker-compose up
, then logs in to the web
container and installs VSCode server stuff.
If it fails, let it reopen locally, and it should show you the log. You can modify the container definitions and try again.
Once it completes, open a terminal. You can npm install
and npm start
there.
The next time you start VSCode in this directory, it’ll offer to Restart in Container. Go with that.
Clean up
When you exit VSCode, it stops the containers, but it does not remove them. This makes startup a lot faster next time.
If you’re done with the project or want to clean up your docker containers, you may want to remove the containers and the database’s volume.
Run docker ps -a
to see all the containers, including stopped one. You’ll see one named <your project>_devcontainer_web_1
and a similar container for db
.
Go to your project root and
docker-compose -f .devcontainer/docker-compose.yml --project-name <your project>_devcontainer down -v
This removes both relevant containers and the volume. (If you want to keep your data around, omit the -v
.)
The end
Okay! Now you have a database, a backend, and a carefully defined and contained development environment.
Don’t miss the part of Tania’s post where she sets the app up for production.
Corrections? DM on Twitter or email jessitron@jessitron.com.
If this was useful, you can support Tania and me on Patreon 🙂