Okej! Du har kanske hört talas om Docker. Men har du hört talas om DevContainers? Paketera hela utvecklingsmiljön in i docker och koppla dig till den helt lagg-fritt från din lokala editor! Det borde vara vanligare än det är - låt oss se vad det handlar om...
Om du är riktigt inne i träsket har du kanske till och med hört talas om Docker compose. Det var i alla fall så långt jag hade kommit i "virtualisering" av min utvecklarvardag. Och ändå satt jag där för 10e gången på ett år och skulle försöka få någon antik kod med ett obskyrt frontend-ramverk att köra. Jag som egentligen bara ville implementera en ny feature i ett av våra interna system ute hos kunden. Slet mig i håret för att jag återigen behöver sätta mig in i byggsystem, dependencies, konventioner, avsaknad av linting/hjälpmedler för just det språket m.m. I grund och botten vet jag ju vad jag ska göra med koden, men att få det runt omkring upp och köra är ofta ett projekt i sig.
Detta är något som man ofta stöter på i rollen som konsult, eller som del av ett team som försöker skapa en snabbare onboardingprocess för ny anställda (eller inhyrda konsulter). Jag trodde detta bara var "som det var", och att lösningen hette "standardisering av projekt-setup" och en bra dokumentation i varje projekts README om hur man bäst kommer igång. Det var därför jag fort blev väldigt intresserad när jag läste om "DevContainers" för första gången.
Här är hiss-säljsnacket (elevator pitch):
Tänk dig att någon som har koll på allt med projektet, går in och konfigurerar din maskin och utvecklingsmiljö helt perfekt för att göra utvecklingsjobbet - du kan hoppa direkt på det som faktiskt är kul - utveckling av business logik/APIer och den nya fina frontenden.
DevContainers är ett sätt att definiera inte bara hur programmet kan köras i Docker, men också allt det runt omkring så som linters/plugins/package-hantering som förväntas finnas på plats i det givna IDE't som används. Jag kommer använda VSCode som exempel, men motsvarande teknologi finns för IntelliJ också.
Hela grundprincipen bygger på att din editor agerar som en "klient" och skickar in dina knapptryck och fil-ändringar in mot en "VSCode" server som kör inuti docker. För en given bas-image som du väljer att använda, tar editorns plugin vid och installerar VSCode servern som ett eget "bygg-steg" efter att din Docker image har byggts färdigt av pluginen. Detta gör att även debugging och liknande bara fungerar helt magiskt.
Låt oss ha ett exempel. Jag är utvecklare på ett team där vi har ett projekt som har en Express webserver och en Postgres databas.
Då kan vi skapa en simpel DevContainer för detta. Filstruktturen ser ut såhär:
All kod ær æven tillgænglig som en workshop på https://github.com/apamildner/devcontainer-ws før hela repoet samlat
En devcontainer fil:
// Inuti devcontainer.json
// För mer information, se https://aka.ms/devcontainer.json.
{
"name": "Node.js & PostgreSQL",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
"forwardPorts": [3000, 5432]
}
Med tillhörande docker-compose.yml
och två Docker filer:
# Inuti Dockerfile,
# Baseras på "bascontainern" från Microsoft
FROM mcr.microsoft.com/devcontainers/javascript-node:1-20-bullseye
# Inuti Dockerfile.pg
FROM postgres:latest
COPY ./pg_init/*.sql /docker-entrypoint-initdb.d
# Inuti docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
volumes:
- ../..:/workspaces:cached
# Overrides default command so things don't shut down after the process ends.
command: sleep infinity
# Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
network_mode: service:db-postgres
db-postgres:
build:
context: .
dockerfile: Dockerfile.pg
restart: unless-stopped
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
POSTGRES_DB: postgres
Om vi nu har ett repo med denna foldern incheckad, och en VsCode IDE där vi har laddat ner denna från CMD-SHIFT-P > Dev Containers
Så är det allt som ska till för att nu köra CMD-SHIFT-P > Dev Container: Rebuild Container Without Cache
Vi kan nu slänga ihop en simpel express applikation:
const express = require('express');
const { Pool } = require('pg');
const app = express();
const port = 3000;
// Database connection configuration
const pool = new Pool({
user: 'postgres',
host: 'localhost',
database: 'postgres',
password: 'postgres',
port: 5432,
});
//Fetch all users from database when /users route is hit
app.get('/users', async (req, res) => {
try {
const client = await pool.connect();
const result = await client.query('SELECT * FROM users');
client.release();
res.json(result.rows);
} catch (err) {
console.error(err);
res.status(500).send('Server error');
}
});
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
});
För att få någon data att jobba med, använder vi en feature av postgres databas-imagen som gör att man kan seeda data om det ligger i en speciell folder, så vi placerar följande i ./pg_init/init.sql
som vi kopierade in under /docker-entrypoint-initdb.d
i Dockerfile.pg
tidigare.
-- Create the 'users' table if it does not exist
-- The table structure: id (SERIAL PRIMARY KEY), name (VARCHAR), email (VARCHAR)
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(100)
);
-- Inserting sample data into the 'users' table
INSERT INTO users (name, email) VALUES ('Alice Smith', 'alice@example.com');
INSERT INTO users (name, email) VALUES ('Bob Johnson', 'bob@example.com');
INSERT INTO users (name, email) VALUES ('Carol Williams', 'carol@example.com');
Eftersom vi har "forwardat" port 3000, kan vi nu besöka localhost:3000/users
- och se där!
Detta är egentligen mest Docker compose magi, i kombination med den "init" featuren från postgres imagen som är ganska cool på egen hand.
Det fina är att vi nu också har den integrerade terminalen i VS Code. Denna kommer koppla upp sig mot den docker compose containern som vi har specificerat som service
i devContainer.json
filen. Här kanske vi skulle vilja använda psql
för att kunna debugga fel i koden också, eller för att manuellt föra in mer data. Låt oss installera psql
i vår devcontainer image:
# inside Dockerfile, used as base container
FROM mcr.microsoft.com/devcontainers/javascript-node:1-20-bullseye
# Add more stuff here that the image for the app may need
RUN apt-get update \
&& apt-get install -y postgresql postgresql-contrib
När vi nu öppnar terminalen i VSCode, kan vi köra psql postgresql://postgres:postgres@localhost:5432/postgres
och vi är nu kopplat mot Postgres databasen från samma container som vår Express app kör. Tänk vad najs nu när din kollega har några problem och du hoppar in på videomötet för att hjälpa: Du vet att hon har psql
installerat så det är bara att börja att felsöka, istället för att sitta och installera det manuellt.
Det finns också stöd för att i devContainer-speccen beskriva vilka extension som ska vara installerade. Inuti devcontainers.json
lägger vi till:
...
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
"settings": {
"editor.tabCompletion": "on"
},
"extensions": [
"streetsidesoftware.code-spell-checker"
]
}
},
...
Vi kan nu köra CMD-SHIFT-P > Dev Containers: Rebuild Container Without Cache, alltså öppna kommand-paletten och köra dev containers kommandot för att bygga om hela DevContainern. När vi nu försöker skriva kod, får vi spell-check på det vi skriver. Vi får också "tab completions" som är en funktion i VsCode för att föreslå kodförslag om man trycker på TAB. Det får också alla andra som försöker röra denna koden och som använder sig av vår DevContainer. Nice!
Varför är inte detta vanligare där ute i det vilda?
Jag antar att det har lite att göra med att det kanske upplevs som en tröskel att sätta upp allt, eller att nuvarande setup fungerar "bra nog" för att det ska vara värt att lägga tid på. En bidragande faktor kan säkert vara att folk har sitt egna uppsätt på maskinen som fungerar för dem. De vill helt enkelt använda sitt IDE på sitt sätt och inte dra in massa config från någon annan. Men, egentligen förstår jag inte varför vi inte bara använder det här hela tiden - det finns inget bättre än när "någon" bara har fixat allt och jag kan börja koda. Samt att man får den skalerings-effekten av att om jag fixar vår DevContainer, så får alla nytta av det direkt. Fått en ny dependency, eller börjat använda en ny tool? In med det i DevContainern så alla kan få använda det. Jag är säker på att jag kommer försöka pusha för DevContainers nästa gång jag är ansvarig för att skapa ett nytt projekt/kodbas. Något vi inte hann gå igenom här, men som också är väldigt intressant, är att det inte finns något behov för att köra detta lokalt. Du kan ha DevContainern körande på en remote maskin eller till exempel GitHub Codespaces, som är en SaaS lösning som baserar sig på detta konceptet. Men mer om det en annan gång!
Har du några insikter/åsikter här? Dela gärna med dig!
Sammanfattning
Vi har nu skrapat lite på ytan av den här nya paradigmen som buntar ihop koden med dess kör-miljö, dependencies OCH utvecklingsmiljö på ett sätt som vi kan återanvända och checka in i Git. Vi har sett hur man med en grundinsats i början på ett projekt kan skapa förutsättningar för vem som helst att fortsätta utvecklingen utan att skapa massa problem på den personliga maskinen. Vi fick också sett lite snabbt på hur man enkelt kan få upp en test-databas och seeda den med data, samt felsöka mellan containers.