---
title: "pgx v5: установка и первое подключение"
description: "Первое подключение к PostgreSQL через pgx v5: go get github.com/jackc/pgx/v5, pgx.Connect, Ping и defer Close, плюс разбор типовых ошибок соединения."
course: "PostgreSQL в Go: драйвер pgx с нуля до продакшена"
url: "https://golangmaster.ru/courses/pgx/lessons/02-ustanovka-podklyuchenie/"
published: "2026-09-19"
updated: "2026-09-21"
minutes: 18
---

# pgx v5: установка и первое подключение

*Курс «PostgreSQL в Go: драйвер pgx с нуля до продакшена», урок 2 из 25. ~18 минут. Обновлено 21.09.2026. Источник: https://golangmaster.ru/courses/pgx/lessons/02-ustanovka-podklyuchenie/*

Настроить pgx connect к PostgreSQL из Go — значит пройти четыре шага: установить модуль, поднять базу, открыть соединение вызовом pgx.Connect и убедиться через Ping, что сервер отвечает. Разберём каждый шаг с реальными командами и программой, которую вы запустите локально, а заодно научимся читать первые ошибки соединения — от connection refused до неверного пароля.

Прежде чем что-то подключать, соберём площадку. Нужен свежий Go — проверьте go version, pgx v5 живёт на актуальных релизах тулчейна, — и работающий PostgreSQL. Ставить базу в систему не обязательно: контейнер Docker поднимает Postgres одной командой, вместе с пользователем, паролем и готовой базой внутри. Дальше — стандартный ритуал любого модуля Go: инициализация модуля и скачивание зависимости. Модуль называется github.com/jackc/pgx/v5, и суффикс v5 здесь обязателен: без него go get скачает четвёртую мажорную версию, у которой другой API, и код этого курса не соберётся. Вместе с основным пакетом подтянутся внутренние зависимости — pgconn и pgtype из того же модуля и пул puddle: они приезжают автоматически, отдельно ставить их не нужно, а в go.mod появится одна строка с версией. Строку require вместе с контрольными суммами go.sum коммитят в репозиторий: сборка на CI воспроизведёт ровно ту же зависимость, что и на ноутбуке. Обновления внутри пятой версии — обычный go get -u, который почти никогда не ломает код: мажорная граница v5 обещает совместимость внутри себя. Строку require вместе с контрольными суммами go.sum коммитят в репозиторий: сборка на CI воспроизведёт ровно ту же зависимость, что и на ноутбуке. Обновления внутри пятой версии — обычный go get -u, который почти никогда не ломает код: мажорная граница v5 обещает совместимость внутри себя. Ещё один плюс: pgx написан на чистом Go без CGO, поэтому кросс-компиляция под любую платформу работает без сюрпризов — приятный бонус для сборки в контейнерах. На этом подготовка закончена: база крутится в контейнере, модуль скачан, остался код.

### Поднимаем базу и ставим модуль

```go
docker run --name pgx-course \
  -e POSTGRES_USER=app \
  -e POSTGRES_PASSWORD=secret \
  -e POSTGRES_DB=app \
  -p 5432:5432 \
  -d postgres:16

go mod init pgx-demo
go get github.com/jackc/pgx/v5
```

Первая команда поднимает контейнер Postgres 16 с пользователем app, паролем secret и базой app на стандартном порту 5432; флаг -d отправляет его в фон. Затем — инициализация модуля и установка pgx v5. Проверить живость базы можно через docker ps, а остановить и удалить — docker stop pgx-course и docker rm pgx-course; данные контейнера при этом исчезнут, что для учебной базы нормально.

### pgx.Connect: первое подключение к Postgres из Go

Главный вызов урока — pgx.Connect(ctx, connString). Первый аргумент — контекст: в v5 он обязателен почти в каждом методе, и именно по контексту потом отменяются зависшие подключения и запросы. Второй — строка подключения, DSN: пользователь, пароль, хост, порт и имя базы в одном значении; в следующем уроке разберём её по косточкам. Импорт нужен один — github.com/jackc/pgx/v5: имя пакета после него остаётся pgx, и никакие псевдонимы в коде не требуются. Connect одновременно открывает TCP-соединение и проводит хендшейк протокола — аутентификацию и согласование параметров, — поэтому возвращает он уже готовое к работе соединение *pgx.Conn, а не заготовку для будущих вызовов. Ошибка возвращается вторым значением, как принято в Go: паник здесь нет и не должно быть, даже если база недоступна. Важно понимать масштаб: *pgx.Conn — это ровно одно соединение с базой. Для CLI-утилит, миграций и разовых скриптов этого достаточно; сервису, который обслуживает HTTP-запросы из разных горутин, понадобится пул — но это тема четвёртого урока, а первые запросы прекрасно работают и на одиночном соединении. Единственное ограничение Conn — время жизни: соединение закрывается вместе с программой или при вызове Close, и держать его открытым «на всякий случай» — плохая привычка: сервер считает каждую сессию.

### Полная программа: подключение, Ping, Close

```go
package main

import (
        "context"
        "fmt"
        "log"

        "github.com/jackc/pgx/v5"
)

func main() {
        ctx := context.Background()

        conn, err := pgx.Connect(ctx, "postgres://app:secret@localhost:5432/app")
        if err != nil {
                log.Fatal(err)
        }
        defer conn.Close(ctx)

        if err = conn.Ping(ctx); err != nil {
                log.Fatal(err)
        }

        fmt.Println("Postgres отвечает")
}
```

Полный каркас: контекст, подключение, отложенное закрытие, пинг. pgx.Connect вернёт ошибку, если база недоступна или не приняла логин и пароль; conn.Close(ctx) освобождает соединение при выходе из main — в v5 Close принимает контекст; conn.Ping отправляет серверу тривиальный запрос и убеждается, что тот живой. Ошибку парсинга DSN и отказ сети видно в одном месте — в err от Connect. Запускайте go run main.go — на экране должна появиться строка «Postgres отвечает».

Что происходит на проводе за эти три вызова, полезно видеть по шагам. Сначала Go открывает TCP-соединение на порт 5432 — здесь рождаются ошибки network-слоя вроде connection refused, когда на порту никто не слушает. Затем клиент отправляет StartupMessage: имя пользователя, имя базы и желаемые параметры протокола. Сервер выбирает метод аутентификации и либо принимает логин, либо отвечает отказом, из которого растут ошибки password authentication failed. Устойчивый SCRAM-SHA-256 не передаёт пароль по сети в открытом виде: клиент доказывает знание секрета обменом вызов-ответ, а сервер уже решает, принимать ли его. После успешного хендшейка сервер объявляет параметры сессии и переходит в состояние готовности — только теперь pgx.Connect возвращает управление вашему коду. Поэтому в тексте ошибки pgx всегда есть контекст: к какому хосту, каким пользователем и на каком шаге всё сломалось — читать её стоит слева направо, а не пугаться длины. Сам хендшейк на локальной машине стоит миллисекунды, но через сеть и вместе с TLS-рукопожатием — уже десятки: это вторая причина, по которой сервис живёт на пуле из четвёртого урока, а не открывает соединения на каждый запрос. Для любопытных: хендшейк заканчивается сообщением ReadyForQuery — с этого момента сервер ждёт SQL, и дальнейший разговор идёт либо расширенным протоколом с плейсхолдерами и типизированными аргументами, либо простым, где стейтмент уходит одной строкой текста. pgx выбирает режим сам: расширенный — для запросов с аргументами, простой — для пачки команд без параметров; это различие ещё выстрелит в уроке про Exec с несколькими стейтментами. Для любопытных: хендшейк заканчивается сообщением ReadyForQuery — с этого момента сервер ждёт SQL, и дальнейший разговор идёт либо расширенным протоколом с плейсхолдерами и типизированными аргументами, либо простым, где стейтмент уходит одной строкой текста. pgx выбирает режим сам: расширенный — для запросов с аргументами, простой — для пачки команд без параметров; это различие ещё выстрелит в уроке про Exec с несколькими стейтментами.

### Ping: зачем проверять соединение отдельно

После хендшейка соединение уже живое — зачем тогда Ping? Причины две. Первая — диагностика: Ping отправляет серверу простейший запрос, так что если между приложением и базой встал прокси, залип DNS или база ушла в режим восстановления, вы увидите это отдельной ошибкой с понятным текстом, а не сюрпризом в первом же пользовательском запросе. Вторая — health-check: в Kubernetes readiness-проба вызывает ровно такую проверку, и под без живой базы не получает трафик от балансировщика. Пинг дешёвый, но не бесплатный: он занимает соединение и делает рейс до сервера, поэтому в readiness-пробе его вызывают с разумным интервалом, а не в каждом обработчике. И про слово «живость»: Ping не проверяет готовность схемы или наличие данных — только то, что сервер отвечает на протокольном уровне. Проверки «миграции накатаны и таблица на месте» пишутся отдельными запросами и к пингу отношения не имеют. И про слово «живость»: Ping не проверяет готовность схемы или наличие данных — только то, что сервер отвечает на протокольном уровне. Проверки «миграции накатаны и таблица на месте» пишутся отдельными запросами и к пингу отношения не имеют. Для разовых проверок у pgx есть шорткат pgx.Ping(ctx, connString): он открывает соединение, пингует и закрывает его одним вызовом — удобно для CLI-скриптов и проверок окружения в CI. Таймаут, в пределах которого всё это обязано уложиться, задаётся не параметром, а контекстом: context.WithTimeout обрежет зависший вызов — подробности в пятнадцатом уроке про таймауты.

> **connection refused: самая первая ошибка каждого.** Если Postgres не поднят, Connect вернёт ошибку вида failed to connect to host=127.0.0.1 user=app database=app: dial tcp 127.0.0.1:5432: connect: connection refused. Читается она слева направо: к кому подключались и на каком шаге сломались. Причина почти всегда прозаична: контейнер не запущен (docker ps пуст), порт не проброшен флагом -p, или программа выполняется в другом контейнере, где localhost — это не хост-машина. Порядок диагностики: docker ps, затем docker logs pgx-course, и только потом правка кода.

### Четыре типовые ошибки первого подключения

- connection refused: база не запущена, порт не проброшен или указан неверный хост
- password authentication failed for user: пароль в DSN не совпал с POSTGRES_PASSWORD контейнера
- database app does not exist: имя базы в DSN отличается от POSTGRES_DB
- TLS-ошибки: сервер требует шифрование, а DSN просит sslmode=disable — вся тема третьего урока
- sorry, too many clients already: лимит соединений сервера исчерпан — вернёмся к нему в уроке про пул

### Одна строка вместо программы: pgx.Ping

```go
err := pgx.Ping(ctx, "postgres://app:secret@localhost:5432/app")
if err != nil {
        log.Fatalf("Postgres недоступен: %v", err)
}
fmt.Println("с базой всё в порядке")
```

pgx.Ping — шорткат для проверки окружения: открыть соединение, отправить пинг, закрыть. Состояния он не возвращает — только ошибку. Такой вызов часто стоит в скриптах CI перед миграциями: сначала убеждаемся, что база вообще доступна, и лишь потом делаем работу. В сервисах с пулом эта функция не нужна — там пинг живёт в Ping от пула.

### Вызовы урока одной таблицей

| Вызов | Что делает | Где уместен |
| --- | --- | --- |
| pgx.Connect | открывает соединение и проводит хендшейк протокола | старте CLI-утилиты, разового скрипта |
| conn.Ping | проверяет живость уже открытого соединения | старте сервиса и readiness-пробе |
| pgx.Ping | открывает, пингует и закрывает соединение одной строкой | проверке окружения в CI и скриптах |
| conn.Close | корректно завершает сессию на сервере | defer сразу после успешного Connect |

### Одно соединение против пула: что выбрать сейчас

pgx.Connect и pgxpool.New принимают одну и ту же строку подключения и возвращают совместимые API: у обоих есть Exec, Query, QueryRow и Begin. Разница — в модели владения. Одиночный Conn — один ресурс, который нельзя дёргать из разных горутин одновременно: конкурентный запрос из второго обработчика упрётся в уже занятое соединение и дождётся своей очереди или упадёт. Пул держит несколько соединений и выдаёт их горутинам по мере необходимости, а при нехватке выстраивает в очередь внутри себя. Правило простое: скрипты и утилиты — Connect, долгоживущие сервисы — pool. Учиться при этом лучше на одиночном соединении: первые уроки курса работают с Conn именно потому, что из него видно каждую ошибку без шума пула, а переезд на пул позже — замена одной строки и одного импорта, вы сделаете его в четвёртом уроке за минуту. Пулам не чужды и тесты: в интеграционных кейсах проще поднять один локальный пул и делить его между тестами, чем открывать соединение в каждом — об этом двадцать третий урок.

> **Типичная ошибка: забыть Close.** Каждое соединение — ресурс на обеих сторонах: в приложении сокет, в Postgres — процесс с памятью. Программа, которая в цикле открывает соединения без закрытия, упрётся в лимит базы и начнёт ловить too many clients. Дисциплина одна: сразу после успешного Connect — defer conn.Close(ctx), сразу после создания пула — defer pool.Close().

### Песочница сайта и локальный запуск

Оговорка о формате курса, честная на весь двадцать пять уроков: код здесь настоящий, с сетью и базой, поэтому в браузерной песочнице он не исполняется — сайт умеет запускать только программы на чистом Go без внешних соединений, и в песочнице каждого урока откроется тематическая программа, иллюстрирующая идею урока. Для рукотворной практики нужны локальный Go и Postgres в Docker — обе команды уже есть в этом уроке. Зато ошибки подключения стоит коллекционировать специально: они приходят в проде в первые же сутки, и умение за десять секунд отличить connection refused от аутентификации экономит часы дежурств. Ритм прохождения простой: сначала разбор и интерактивы прямо на странице — они работают без базы и засчитывают XP, затем копирование кода в терминал и эксперименты с ошибками из списка перед глазами.

### Проверка: поведение Connect

**Вопрос:** Что вернёт pgx.Connect, если Postgres не запущен на указанном хосте?

- Панику — соединение это критическая операция
- ✅ Нулевое соединение и ошибку вида connection refused
- Соединение без ошибки — проверять надо отдельно через Ping
- Зависнет навсегда, пока база не поднимется

pgx.Connect возвращает (*pgx.Conn, error): при отказе сети хендшейк не проходит, соединение равно nil, а в err лежит причина — обычно dial tcp ... connection refused. Проверяйте ошибку сразу и выходите из программы, не доводя до нильской паники в QueryRow.

### Проверка: зачем Ping

**Вопрос:** Зачем после успешного Connect вызывать Ping?

- Connect не устанавливает соединение, а только создаёт объект
- ✅ Чтобы удостовериться, что сервер отвечает на запросы, а не только принял хендшейк
- Ping нужен только в тестах, в проде его не вызывают
- Без Ping база закроет соединение через минуту

Connect уже проводит хендшейк, но Ping проверяет обработку запроса сквозь всё: прокси, балансировщик и состояние базы, например режим recovery. Тот же паттерн живёт в readiness-пробах: под получает трафик только после успешного пинга.

### Что напечатает программа?

```go
conn, err := pgx.Connect(ctx,
    "postgres://app:wrongpass@localhost:5432/app")
if err != nil {
    fmt.Println("ошибка:", err)
    return
}
defer conn.Close(ctx)

fmt.Println("подключено")
```

**Что выведет программа?**

- подключено
- ✅ ошибка: password authentication failed for user app
- паника nil pointer
- ошибка: connection refused

Хост и порт верные, TCP-соединение устанавливается, но пароль не проходит аутентификацию — сервер отвечает отказом во время хендшейка, и Connect возвращает её вторым значением. Это не connection refused: сетевой слой жив, проблема в креденшалах. Печатается текст ошибки и происходит выход — defer не сработает, потому что соединения нет.

### Соберите подключение

Восстановите каркас: контекст, подключение, закрытие и проверка связи.

```go
ctx := context.___()

conn, err := pgx.___(ctx, "postgres://app:secret@localhost:5432/app")
if err != nil {
        log.Fatal(err)
}
defer conn.___(ctx)

if err = conn.___(ctx); err != nil {
        log.Fatal(err)
}
```

Подсказка: Контекст начинается с Background, соединение открывает pgx.Connect, закрывает Close, а живость проверяет Ping.

Правильные ответы в пропусках: `Background`, `Connect`, `Close`, `Ping`

### Соберите main.go из строк

Расставьте строки в порядке исполнения: подключение, закрытие, пинг, вывод.

Правильный порядок строк:

1. `ctx := context.Background()`
2. `dsn := "postgres://app:secret@localhost:5432/app"`
3. `conn, err := pgx.Connect(ctx, dsn)`
4. `if err != nil { log.Fatal(err) }`
5. `defer conn.Close(ctx)`
6. `err = conn.Ping(ctx)`
7. `fmt.Println("Postgres отвечает")`

### Карточки: первое подключение

- **pgx.Connect** — Открывает соединение с Postgres и проводит хендшейк протокола. Возвращает *pgx.Conn и error — паник нет даже при недоступной базе.
- ***pgx.Conn** — Одно соединение с базой. Не годится для конкурентных запросов из нескольких горутин — сервису нужен пул pgxpool.
- **conn.Ping(ctx)** — Отправляет серверу тривиальный запрос и проверяет живость соединения. Основа health-check и readiness-проб.
- **connection refused** — Ошибка TCP-слоя: на хосте и порту никто не слушает. База не запущена, порт не проброшен или хост неверный.
- **pgx.Ping** — Шорткат: открыть соединение, пингануть, закрыть — одной строкой. Удобен для проверки окружения в CI.
- **defer conn.Close(ctx)** — Освобождение соединения при выходе из функции. В pgx v5 Close принимает контекст, как и остальные методы.

Соединение открыто и проверено — дальше по курсу: в [третьем уроке](/courses/pgx/lessons/03-conn-string-dsn/) разберём строку подключения по косточкам — параметры sslmode, переменные окружения и сборка DSN без секретов в коде. В [четвёртом](/courses/pgx/lessons/04-pool-soedinenij/) пересаживаемся с одиночного Conn на пул соединений и настраиваем лимиты под нагрузку. В [пятом](/courses/pgx/lessons/05-prostye-zaprosy/) наконец отправим настоящие INSERT и SELECT. Если контексты, ошибки и defer пока в новинку — подтяните их в [базовом курсе Go](/courses/go-basics/), там этим темам посвящены отдельные уроки: без них половина магии pgx останется непонятой. Пятнадцать-восемнадцать минут на урок — и к концу недели у вас работает связка Go плюс Postgres.

## Коротко: что запомнить

- Модуль ставится командой go get github.com/jackc/pgx/v5 — суффикс версии обязателен.
- pgx.Connect(ctx, dsn) открывает соединение и проводит хендшейк; возвращает *pgx.Conn и error.
- conn.Ping(ctx) проверяет, что сервер отвечает на запросы; тот же вызов — основа readiness-проб.
- defer conn.Close(ctx) ставится сразу после успешного подключения — соединения не должны утекать.
- connection refused — база не запущена или порт не проброшен; password authentication failed — неверные креденшалы.
- Скриптам — одиночное соединение, долгоживущим сервисам — пул pgxpool из четвёртого урока.
- Код курса запускается локально с Go и Postgres в Docker; песочница показывает программу на чистом Go.

## Домашнее задание

Поднимите контейнер командой из урока, соберите main.go и добейтесь строки «Postgres отвечает». Затем поэкспериментируйте с ошибками: поменяйте пароль в DSN на неверный и сравните текст ошибки с тем, что напечатает программа при остановленном контейнере. Найдите в каждом сообщении pgx хост, пользователя и базу — научиться читать эти строки за минуту стоит любой дальнейшей отладки.

## Вопросы и ответы по уроку

### Почему pgx.Connect, а не sql.Open?

sql.Open из database/sql ленив: он не подключается, пока не понадобится первый запрос, и часть ошибок соединения откладывается на потом. pgx.Connect делает всю работу сразу и возвращает готовое соединение или честную ошибку — для CLI и первых шагов это удобнее. Для сервисов с нагрузкой выбор всё равно за pgxpool — одиночное соединение здесь только учебная ступень, а тот же fail-fast для пула даёт пинг при старте.

### Нужно ли закрывать соединение в конце main?

На практике процесс всё равно умрёт вместе с сокетами, но закрывать — правильная дисциплина: defer conn.Close(ctx) гарантирует корректное завершение сессии на сервере, а в длинных программах без Close соединения утекут и упрутся в лимит базы. Сомневаетесь — проверьте: pg_stat_activity покажет зависшие сессии от программ, забывших Close.

### Можно ли подключаться без пароля?

Можно, если сервер настроен на trust- или peer-аутентификацию — так бывает в локальной разработке и CI. В DSN тогда достаточно user и dbname. Но для прод-контура аутентификация по паролю или сертификату обязательна, как и sslmode — об этом следующий урок. Сомневаетесь, какой метод настроен на сервере, — ответ в pg_hba.conf: там для каждого хоста прописан свой способ. И даже в доверенной сети disable стоит ставить осознанно, а не по умолчанию.
