Learning SQL by reading is like learning to swim by watching videos: necessary, but not enough. For this course to be genuinely useful you need an environment where you can write queries, get them wrong, read the error message and try again. This lesson walks you from "I have nothing installed" to "I have PostgreSQL 16 running, I know how to connect and I've created the greenstore database". We'll cover installation on Linux, macOS and Windows, the Docker alternative (the one we recommend), an emergency exit with SQLite if you can't install anything on your machine, the essential commands of the psql client and the most widely used graphical clients.

Contents

  1. What exactly you need
  2. Option A (recommended): PostgreSQL with Docker
  3. Option B: native installation on Linux, macOS and Windows
  4. Option C (minimal): SQLite
  5. Connecting with psql and its basic commands
  6. Graphical clients: pgAdmin, DBeaver and VS Code
  7. Creating the greenstore database and a user of your own
  8. Checking that everything works
  9. Common Mistakes and Tips
  10. Exercises
  11. Conclusion

  1. What exactly you need

To follow the course you need three pieces:

Piece What it is Course recommendation
The server The process that stores the data and runs the queries PostgreSQL 16
A command-line client To write queries and see results psql (ships with PostgreSQL)
A graphical client (optional) To browse tables comfortably DBeaver or pgAdmin

And you need to decide how you're going to install the server. These are the three routes, ordered by what we recommend:

graph TD
    A[Can you install software<br/>on your machine?] -->|Yes| B[Do you have Docker<br/>or can you install it?]
    A -->|No| E[Option C: SQLite<br/>or PostgreSQL in the cloud]
    B -->|Yes| C[Option A: Docker<br/>RECOMMENDED]
    B -->|No| D[Option B: native<br/>system installation]
Option Advantages Drawbacks
A. Docker Exact, reproducible version; deletes without a trace; identical on all three operating systems Requires Docker installed
B. Native Starts with the system; no intermediate layer Depends on the system; uninstalling cleanly is more awkward
C. SQLite Zero server installation; a single file A more limited dialect: you won't see users, strict types or a good part of modules 8, 9 and 10

  1. Option A (recommended): PostgreSQL with Docker

Docker lets you spin up PostgreSQL 16 in an isolated container, with exactly the version the course uses, without touching the rest of your system.

2.1. Starting the container

docker run --name pg-course \
  -e POSTGRES_PASSWORD=course2026 \
  -e POSTGRES_USER=postgres \
  -e POSTGRES_DB=postgres \
  -p 5432:5432 \
  -v pgdata-course:/var/lib/postgresql/data \
  -d postgres:16

It's worth understanding each option, because you'll see them again:

Option What it does
--name pg-course Gives the container a name so you can refer to it later
-e POSTGRES_PASSWORD=... Superuser password. Mandatory: without it the container won't start
-e POSTGRES_USER=postgres Superuser name (the default is already postgres)
-p 5432:5432 Publishes the container's port on your machine, so external clients can connect
-v pgdata-course:/var/lib/postgresql/data Persistent volume: without this you'd lose all your data when you delete the container
-d Runs in the background (detached)
postgres:16 Official image, version 16

2.2. Checking that it's running

docker ps
docker logs pg-course --tail 20

In the logs you should see a line similar to database system is ready to accept connections.

2.3. Container life cycle

docker stop pg-course      # Shut down (the data is still there)
docker start pg-course     # Turn it back on
docker rm -f pg-course     # Delete the container (the pgdata-course volume survives)
docker volume rm pgdata-course   # Delete the data TOO. Be careful with this one.

Note: if port 5432 is already taken by another PostgreSQL installation, change the publication to -p 5433:5432 and use -p 5433 when connecting.

  1. Option B: native installation on Linux, macOS and Windows

System Installation commands How it starts Notes
Debian / Ubuntu sudo apt update
sudo apt install postgresql-16 postgresql-client-16
sudo systemctl start postgresql
sudo systemctl enable postgresql
The postgres system user is created; you get in with sudo -u postgres psql
Fedora / RHEL sudo dnf install postgresql16-server postgresql16
sudo /usr/pgsql-16/bin/postgresql-16-setup initdb
sudo systemctl enable --now postgresql-16 The cluster has to be initialised explicitly
Arch Linux sudo pacman -S postgresql
sudo -u postgres initdb -D /var/lib/postgres/data
sudo systemctl enable --now postgresql
macOS (Homebrew) brew install postgresql@16 brew services start postgresql@16 Creates a database named after your user
macOS (Postgres.app) Download from postgresapp.com and drag to Applications "Start" button The simplest way on a Mac; you have to add the binary to your PATH
Windows (EDB installer) Download the installer from enterprisedb.com and follow the wizard Windows service, starts on its own Includes pgAdmin 4 and the Stack Builder
Windows (winget) winget install PostgreSQL.PostgreSQL.16 Windows service The quick route with no graphical wizard

Details that tend to cause trouble:

  • Linux/Debian: after installing, local access goes through peer authentication. The right way in the first time is sudo -u postgres psql, not plain psql.
  • macOS with Homebrew: if psql isn't found, add the path to your PATH:
    echo 'export PATH="/opt/homebrew/opt/postgresql@16/bin:$PATH"' >> ~/.zshrc
    source ~/.zshrc
    
  • Windows: the installer doesn't always add psql to the PATH. If PowerShell tells you the command isn't recognised, add C:\Program Files\PostgreSQL\16\bin to the Path environment variable by hand, or use the "SQL Shell (psql)" shortcut in the Start menu.

  1. Option C (minimal): SQLite

If you work on a machine where you can't install services, SQLite will let you follow a good part of the course. A SQLite database is a single file and there's no server to start.

# Linux (Debian/Ubuntu)
sudo apt install sqlite3

# macOS: already installed
sqlite3 --version

# Windows (winget)
winget install SQLite.SQLite

Creating a database and opening it are the same thing:

sqlite3 greenstore.db

Its client commands start with a dot instead of a backslash:

Task psql (PostgreSQL) sqlite3
List tables \dt .tables
Describe a table \d products .schema products
Quit \q .quit
Run a file \i file.sql .read file.sql
Help \? .help

Honest limitations of SQLite for this course: it has no users or permissions (module 11), its types are dynamic and very permissive (modules 1 and 5), it doesn't support RIGHT/FULL OUTER JOIN in versions before 3.39, its EXPLAIN is different (module 8) and it has no equivalent stored procedures or triggers (module 10). Use it as a last resort; if at any point you can switch to PostgreSQL, do it.

  1. Connecting with psql and its basic commands

psql is the official command-line client. It's the tool we'll use in the explanations because it's fast, it's always available and it shows errors exactly as the server produces them.

5.1. Connecting

# Native installation on Linux, the first time
sudo -u postgres psql

# Explicit connection specifying server, port, user and database
psql -h localhost -p 5432 -U postgres -d postgres

# If you use Docker, you can go straight into the container
docker exec -it pg-course psql -U postgres -d postgres

# A full connection string (URI) works too
psql "postgresql://postgres:course2026@localhost:5432/postgres"

The options repeat across almost every PostgreSQL tool:

Option Meaning Usual value
-h Server host localhost
-p Port 5432
-U User postgres
-d Database to connect to greenstore
-f Run a .sql file and quit -f greenstore.sql
-c Run a single statement and quit -c "SELECT version();"

Once you're inside, the prompt tells you the state you're in:

postgres=#    -- connected to the "postgres" DB as superuser
greenstore=>  -- connected to "greenstore" as a normal user
greenstore-#  -- UNFINISHED statement: the semicolon is missing
greenstore'#  -- you have an open single quote

That third prompt (-#) is the one that puzzles people most at the start: it means psql is waiting for you to close the statement with ;.

5.2. The metacommands you can't do without

Commands that start with \ are not SQL: the client itself interprets them, which is why they take no semicolon.

Command What it does
\l Lists every database on the server
\c greenstore Connects to the greenstore database
\dt Lists the tables in the current schema
\d products Describes the products table: columns, types, keys and indexes
\dn Lists the schemas
\du Lists the users and roles
\df Lists the functions
\i file.sql Runs a SQL file
\x Toggles expanded view (one column per line): a lifesaver with wide tables
\timing Turns the execution time of each query on/off
\e Opens the last query in your text editor
\? Help for the metacommands
\h SELECT Syntax help for a specific SQL statement
\q Quit

Example of a typical session:

postgres=# \l
postgres=# \c greenstore
You are now connected to database "greenstore" as user "postgres".
greenstore=# \dt
greenstore=# \d products
greenstore=# \timing
Timing is on.
greenstore=# \q

  1. Graphical clients: pgAdmin, DBeaver and VS Code

A graphical client doesn't replace psql, but it speeds up exploring an unfamiliar schema enormously.

Client Engines it supports Strong point Weak point Ideal for
pgAdmin 4 PostgreSQL only Full server administration; ships with the Windows installer Heavy web interface; not nimble for writing queries Administering PostgreSQL in depth
DBeaver Community PostgreSQL, MySQL, SQLite, Oracle, SQL Server and dozens more One client for everything; good editor with autocompletion and ER diagrams Uses a fair amount of memory (it's Java) The course's general recommendation
VS Code SQL extension PostgreSQL, MySQL, SQLite (depending on the extension) Queries inside the editor where you already code; .sql files versionable in Git Fewer administration features Anyone who already lives in VS Code
TablePlus / DataGrip Multiple Very polished and fast Paid Daily professional use

Connection details any of them will ask you for (with the Docker setup from section 2):

Field Value
Host localhost
Port 5432
Database greenstore
User postgres (or sql_course, see section 7)
Password course2026

  1. Creating the greenstore database and a user of your own

Let's prepare the course workspace. Connect first as superuser:

psql -h localhost -U postgres -d postgres

And run:

-- 1. Create a user of your own for the course, instead of working as superuser
CREATE USER sql_course WITH PASSWORD 'course2026';

-- 2. Create the course database, with that user as its owner
CREATE DATABASE greenstore
    OWNER sql_course
    ENCODING 'UTF8'
    TEMPLATE template0
    LC_COLLATE 'en_US.UTF-8'
    LC_CTYPE 'en_US.UTF-8';

What each part does:

  • CREATE USER creates a role that can log in. Working as superuser every day is a bad habit: an accidental DROP would meet no barrier at all.
  • OWNER sql_course makes that user the owner of the database so it can create tables inside without extra permissions.
  • ENCODING 'UTF8' guarantees that the accented characters in our data (Castellón, Lucía Martínez Soler) are stored correctly.
  • LC_COLLATE defines the language's alphabetical order: with the English setting, ORDER BY will place "apple" before "Banana", as you'd expect, instead of grouping all the capitals first.

If LC_COLLATE gives you an error: in the PostgreSQL Docker image that locale may not have been generated. In that case, create the database without those two clauses:

CREATE DATABASE greenstore OWNER sql_course ENCODING 'UTF8';

It affects nothing in the course beyond small nuances of alphabetical ordering.

Now connect to the new database with your user:

psql -h localhost -U sql_course -d greenstore

And grant permissions on the public schema (necessary since PostgreSQL 15, which tightened the default permissions). This runs as superuser:

GRANT ALL ON SCHEMA public TO sql_course;

  1. Checking that everything works

The canonical check is to ask the server for its version:

SELECT version();

Expected result (the exact text will vary depending on your system):

version
PostgreSQL 16.4 on x86_64-pc-linux-gnu, compiled by gcc 12.2.0, 64-bit

A couple more checks, useful for confirming you're where you think you are:

SELECT current_database(), current_user, now();
current_database current_user now
greenstore sql_course 2026-02-25 10:14:07.412+01

And a quick test that you can create objects in the database:

CREATE TABLE test_table (id INTEGER, content TEXT);
INSERT INTO test_table VALUES (1, 'Hello GreenStore');
SELECT * FROM test_table;
DROP TABLE test_table;
id content
1 Hello GreenStore

If the four statements run without error, your environment is ready. With DROP TABLE test_table; you leave the database clean for the real script, which you'll load in lesson 01-06.

Checklist

Check Command Expected result
The server responds SELECT version(); A line with "PostgreSQL 16"
The database exists \l greenstore appears in the list
You're connected to it SELECT current_database(); greenstore
You can create tables CREATE TABLE test_table (...) CREATE TABLE
The graphical client connects Test the connection in DBeaver/pgAdmin Connection successful

Common Mistakes and Tips

  • psql: error: connection to server ... failed: Connection refused. The server isn't running or the port is a different one. With Docker, check docker ps; with a native installation, sudo systemctl status postgresql.
  • FATAL: password authentication failed for user "postgres". Wrong password, or you're using the one from another environment. In Docker, the password is the one you passed in POSTGRES_PASSWORD the first time the volume was created: changing it in a later docker run has no effect if the volume already exists.
  • FATAL: role "your_user" does not exist. On Linux, psql tries to connect with your system user. Use -U postgres explicitly or get in with sudo -u postgres psql.
  • FATAL: database "greenstore" does not exist. You haven't created it yet, or you created it on a different server from the one you're querying.
  • Forgetting the semicolon. If the prompt goes from =# to -#, psql is waiting for the ;. Type it and press Enter.
  • Losing your data when recreating the container. Without -v there's no volume, and deleting the container deletes the whole database. It's the most painful Docker mistake.
  • Tip: save your queries in .sql files. Create a sql-course/ folder with one file per module. You'll be able to run them with \i and you'll keep a record of your progress.
  • Tip: turn \timing on from day one. You'll get used to looking at how long each query takes, something that will feel natural by the time you reach module 8.
  • Tip: use \x with wide tables. When a row doesn't fit on screen and the result is unreadable, \x turns it into a vertical list of fields.

Exercises

Exercise 1

Start PostgreSQL 16 with Docker on port 5433 instead of 5432 (useful if you already have another PostgreSQL on your machine), with the container name pg-greenstore and a volume called gs-data. Then connect with psql and check the server version.

Exercise 2

From psql, and without leaving the client, answer these three questions using metacommands (not SQL):

  1. Which databases exist on your server?
  2. Which users/roles are defined?
  3. What is the full syntax of the CREATE DATABASE statement?

Exercise 3

Create a database called sql_tests owned by the sql_course user, connect to it, create a notes(id, content) table, insert two rows, query them and finally drop the whole database.

Solutions

Solution 1

docker run --name pg-greenstore \
  -e POSTGRES_PASSWORD=course2026 \
  -p 5433:5432 \
  -v gs-data:/var/lib/postgresql/data \
  -d postgres:16

The key detail is -p 5433:5432: the number on the left is the port on your machine and the one on the right the container's (which is always 5432, because inside the container PostgreSQL listens there). To connect:

psql -h localhost -p 5433 -U postgres -d postgres
SELECT version();

Solution 2

greenstore=# \l
greenstore=# \du
greenstore=# \h CREATE DATABASE

\l (for list) shows databases with their owner and encoding; \du (display users) shows roles and their attributes; \h gives SQL syntax help, whereas \? gives the metacommand help. Remember: none of them takes a semicolon, because they aren't SQL.

Solution 3

psql -h localhost -U postgres -d postgres
CREATE DATABASE sql_tests OWNER sql_course;
postgres=# \c sql_tests
CREATE TABLE notes (id INTEGER, content TEXT);
INSERT INTO notes VALUES (1, 'First note'), (2, 'Second note');
SELECT * FROM notes;
id content
1 First note
2 Second note

To drop the database you can't be connected to it, so first go back to postgres:

sql_tests=# \c postgres
DROP DATABASE sql_tests;

If you try the DROP while inside, PostgreSQL answers ERROR: cannot drop the currently open database. It's a very frequent error and the fix is always the same: switch database first.

Conclusion

You now have a real working environment:

  • You know the three routes to having PostgreSQL 16: Docker (reproducible and recommended), native installation per operating system and SQLite as a last resort.
  • You know the psql client, its connection options (-h, -p, -U, -d, -f, -c) and its key metacommands (\l, \c, \dt, \d, \x, \timing, \?, \q).
  • You can choose a graphical client on solid grounds: DBeaver as the general option, pgAdmin for administration, VS Code if you already work there.
  • You've created the greenstore database and the sql_course user, and you've verified the installation with SELECT version();.

In the next lesson, Basic SQL Syntax, we'll stop installing and start writing: you'll see how a SQL statement is built on the inside (keywords, identifiers, literals, operators), what role the semicolon plays, how PostgreSQL treats upper and lower case, how to comment your code, what formatting conventions professionals use and —very importantly— how to read an error message so you know exactly where you went wrong.

SQL Course

Module 1: Introduction to SQL

Module 2: Basic SQL queries

Module 3: Working with multiple tables

Module 4: Advanced data filtering

Module 5: Data manipulation

Module 6: Advanced SQL functions

Module 7: Subqueries and nested queries

Module 8: Indexes and performance tuning

Module 9: Transactions and concurrency

Module 10: Advanced topics

Module 11: SQL in practice

Module 12: Final project

© Copyright 2026. All rights reserved