---
title: "Complete Guide to Debugging Lua in VSCode (with OpenResty Example)"
description: "How to set breakpoints and debug Lua in VSCode — must-install extensions, common debugging techniques, and a complete real-world OpenResty workflow."
canonical_url: "https://blog.markkulab.net/en/post/vscode-lua-debug-openresty"
author: "Mark Ku"
author_url: "https://blog.markkulab.net/en/author/mark-ku"
site: "Mark Ku's Tech Notes"
date_published: "2025-07-13 06:01:00 +0800"
category: "DevOps"
tags: ["lua", "vscode", "openresty", "emmylua", "docker", "debugging"]
language: "en"
license: "CC BY 4.0"
license_url: "https://creativecommons.org/licenses/by/4.0/"
attribution: "when reusing or quoting, credit the author and link back to the original"
---

# Complete Guide to Debugging Lua in VSCode (with OpenResty Example)

## Complete Guide to Debugging Lua in VSCode (with OpenResty Example)

## Why learn Lua debugging?

Lua is widely used in embedded systems, games, and high-performance scenarios like Nginx/OpenResty. Lua is lightweight to write, but bug-hunting without good tools is painful. This article walks through setting up a modern Lua debugging environment in VSCode — set breakpoints, step through code, inspect variables in real time. Big efficiency boost!

---

## Must-install VSCode extensions

- [EmmyLua](https://marketplace.visualstudio.com/items?itemName=tangzx.emmylua): Lua intellisense and breakpoint debugging

---

## Best practice: VSCode + Docker for Lua debugging (using OpenResty)

This project actually uses VSCode + Docker + EmmyLua for Lua breakpoint debugging. Here's the complete workflow and configuration:

### 1. Project structure and key files

- `docker-compose.yml`: defines the openresty service, port mapping, volume mounts
- `conf/nginx.conf`: Nginx and OpenResty configuration
- `lua/myapp.lua`: main Lua business logic and debug entry point
- `lua/.vscode/launch.json`: VSCode debug configuration

### 2. Docker port and volume setup

`docker-compose.yml` should include:

```yaml
version: '3.8'
services:
  openresty:
    build: .
    container_name: openresty-dev
    ports:
      - "8080:80"      # HTTP
      - "8081:443"     # HTTPS
      - "9966:9966"    # EmmyLua Debugger (Lua debugging)
    volumes:
      - ./conf/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf:ro
      - ./lua:/usr/local/openresty/nginx/lua
      - ./logs:/usr/local/openresty/nginx/logs
      - ./db/GeoLite2-City.mmdb:/usr/local/openresty/nginx/lua/GeoLite2-City.mmdb:ro
    environment:
      TZ: Asia/Taipei
    restart: unless-stopped 
```

- `8080:80`: external HTTP service
- `8081:443`: external HTTPS service (if SSL is configured)
- `9966:9966`: Lua debugging (EmmyLua Debugger port)
- `TZ: Asia/Taipei`: container timezone — useful for log alignment
- `restart: unless-stopped`: auto-restart container unless manually stopped
- Adjust other volume mounts to match your project paths

### 2.5 Sample Dockerfile

Below is the Dockerfile for OpenResty + Lua debugging used in this project — bundled with lua-resty-maxminddb, EmmyLua Debugger, GeoLite2-City database, etc.:

```dockerfile
FROM openresty/openresty:alpine-fat

# Install required packages and tools
RUN apk add --no-cache \
    git \
    build-base \
    cmake \
    libmaxminddb-dev \
    perl \
    libmaxminddb \
    wget \
    tar \
    unzip \
    luarocks

# Install lua-resty-maxminddb
RUN luarocks install lua-resty-maxminddb

# Set timezone
ENV TZ=Asia/Taipei

# Create directory structure
RUN mkdir -p /usr/local/openresty/nginx/lua \
    && mkdir -p /usr/local/openresty/nginx/logs \
    && mkdir -p /usr/local/openresty/nginx/db

# Download latest MaxMind GeoLite2-City database
RUN wget -O /tmp/GeoLite2-City.tar.gz "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=<YOUR_LICENSE_KEY>&suffix=tar.gz" \
    && tar -xzf /tmp/GeoLite2-City.tar.gz -C /tmp \
    && find /tmp -name "GeoLite2-City.mmdb" -exec cp {} /usr/local/openresty/nginx/db/ \; \
    && rm -rf /tmp/GeoLite2-City.tar.gz /tmp/GeoLite2-City_*

# === Download and build emmy_core.so ===
WORKDIR /tmp
RUN git clone https://github.com/EmmyLua/EmmyLuaDebugger.git \
    && cd EmmyLuaDebugger \
    && mkdir build && cd build \
    && cmake .. -DCMAKE_BUILD_TYPE=Release -DLUA_INCLUDE_DIR=/usr/local/openresty/luajit/include/luajit-2.1 \
    && make \
    && find . -name emmy_core.so -exec cp {} /usr/local/openresty/lualib/emmy_core.so \; \
    && cd / && rm -rf /tmp/EmmyLuaDebugger

# Set Lua module paths
ENV LUA_PATH="/usr/local/openresty/lualib/?.lua;;"
ENV LUA_CPATH="/usr/local/openresty/lualib/?.so;;"

# Default startup
CMD ["/usr/local/openresty/bin/openresty", "-g", "daemon off;"]
```

> **Note:**
> - For `license_key=<YOUR_LICENSE_KEY>`, register at [MaxMind](https://www.maxmind.com/) to get your dedicated key.
> - This key is personal — don't expose it publicly or commit to version control.
> - Without a valid key, the GeoLite2-City database can't be downloaded.

### 3. Enable debugging in your Lua code

At the top of `lua/myapp.lua`, add:

```lua
local dbg = require("emmy_core")
dbg.tcpListen("0.0.0.0", 9966)  -- have the in-container debugger listen on all interfaces
dbg.waitIDE()                   -- wait for the IDE to connect before continuing
dbg.breakHere()                 -- enter breakpoint
```

### 4. VSCode debug configuration

Add to `lua/.vscode/launch.json`:

```json
{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "emmylua_new",
            "request": "attach",
            "name": "Attach by process id",
            "pid": 0,
            "processName": "",
            "captureLog": false,
            "host": "localhost",
            "port": 9966,
            "cwd": "${workspaceFolder}/lua",
            "ext": [".lua", "lua.txt", ".lua.bytes"]
        }
    ]
}
```
> Set `host` to `localhost` since we've already mapped port 9966 from the container to the host.

### 5. Startup and debugging flow

1. Restart the openresty container (`docker-compose restart openresty`).
2. Start debugging in VSCode (F5), select the attach config from above.
3. Trigger the corresponding HTTP request (e.g., `http://localhost:8080/`); execution will stop at the breakpoint.
4. Happy step-debugging!

---

## [Worked example] Debugging with myapp.lua

Using `lua/myapp.lua` as an example, here's how to set breakpoints and inspect variables:

```lua
local dbg = require("emmy_core")
dbg.tcpListen("0.0.0.0", 9966)
dbg.waitIDE()
dbg.breakHere()

local cjson = require 'cjson'
local geo = require 'resty.maxminddb'
geo.init("/usr/local/openresty/nginx/lua/GeoLite2-City.mmdb")

-- Suppose this function looks up the IP location
local function get_country(ip)
    local res, err = geo.lookup(ip)
    if not res then
        ngx.log(ngx.ERR, "Geo lookup error: ", err)
        return nil
    end
    return res
end

local ip = ngx.var.arg_ip or ngx.var.remote_addr
local country_info = get_country(ip)
ngx.say(cjson.encode(country_info))
```
> Just insert the debug code at the top of myapp.lua, and you can step through every variable with VSCode breakpoints!

---

## [Nginx config example] Routing requests to myapp.lua

Next, configure nginx.conf to route requests through OpenResty to myapp.lua:

```nginx
location /lua {
    default_type 'text/plain';
    content_by_lua_file /usr/local/openresty/nginx/lua/myapp.lua;
}
```

> With this config, when you visit http://localhost:8080/lua, Nginx routes the request to myapp.lua and returns the result.

---

> When a client (browser, curl) requests http://localhost:8080/lua, Nginx hands the request to myapp.lua and returns the result. You can also adjust routing as needed.

![Debug screenshot](https://blog.markkulab.net/content/markku/posts/vscode-lua-debug-openresty/images/screenshot.png)


## GitHub sample project

For complete sample code, see: [https://github.com/markku636/openresty-deubug](https://github.com/markku636/openresty-deubug)


---

## Common Lua debugging techniques

1. **Check the Error Log**  
   `/usr/local/openresty/nginx/logs/error.log`  
   First place to look for issues.
2. **Enable Debug Log**  
   - OpenResty: set `error_log ... debug;` in `nginx.conf`
3. **Add log calls in Lua scripts**  
   `ngx.log(ngx.ERR, "Debug info: ", cjson.encode(var))`
4. **API testing**  
   Use Postman/curl to call APIs and inspect responses.
5. **VSCode debug attach failing?**  
   - Confirm `dbg.tcpListen("0.0.0.0", 9966)`, not `localhost`.
   - Verify `9966:9966` is in `docker-compose.yml`.
   - VSCode's `host` should be set to `localhost`.
   - Check whether firewall or security software is blocking the port.
6. **Logs not visible?**  
   - Check `/usr/local/openresty/nginx/logs/error.log`.
   - Lua scripts can use `ngx.log(ngx.ERR, "debug info")` as an aid.

---



## Remote debugging in containers (Docker)

| Method | Command / Description |
|------|-----------|
| Get inside container & tail logs | `docker exec -it openresty-dev /bin/sh`<br/>`tail -f /usr/local/openresty/nginx/logs/error.log` |
| View container logs directly | `docker logs -f openresty-dev` |
| Mount local directory | `docker run -v /your/local/logs:/usr/local/openresty/nginx/logs ... openresty` |
| Capture traffic | `sudo tcpdump -i docker0 port 8080` |
| VSCode Remote | Edit and debug files inside the container via VSCode |

---

## About this article and its author

Originally published on [Mark Ku's Tech Notes](https://blog.markkulab.net/en/post/vscode-lua-debug-openresty)

License: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) — when reusing or quoting, credit the author and link back to the original

### About the author

**[Mark Ku](https://blog.markkulab.net/en/author/mark-ku)** — Software Solution Provider

- 10+ years senior software engineer, now an AI Builder
- Focused on large-platform architecture — North-American e-commerce, AI SaaS subscription billing
- Combining AI Agents and automation to build evolvable product foundations

### Free tools built by the author

All of these are free to use:

- [Free PDF Sign Tool](https://blog.markkulab.net/en/tools/pdf-sign): Online PDF sign tool — draw, type, or upload a signature, then drag, resize, and download. Everything runs in your browser; nothing is uploaded.
- [VS Code Refactory](https://blog.markkulab.net/en/tools/refactory): Refactory is a VS Code refactoring extension: 34 actions plus a 37-rule code-smell inspection layer with a Code Health dashboard, across 18 languages, backed by 534 tests. It learns your repo's conventions: where interfaces live, where DI is registered, whether 'use client' belongs. It ranks files by git churn × complexity so you know what to fix first, and hands any smell to the Claude Code already on your machine. Free to use, and your source never leaves your computer.
- [DB-Kit Database Manager](https://blog.markkulab.net/en/tools/db-kit): DB-Kit is a lightweight, cross-platform database manager built with Tauri + Rust + React. Manage MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, SQLite, MongoDB, Redis, Kafka, Elasticsearch and RabbitMQ from one consistent interface: passwords encrypted in the OS keychain, SSH tunnels, full CRUD, a visual query builder, stacked multi-statement result sets, cross-connection data transfer and compare/sync, Excel / CSV import & export, visualized execution plans, ER diagrams, scheduled backups, SQL stress testing with p50–p99 latency percentiles, a 15-rule SQL review engine, Kafka message browsing with monitoring & alerts, a bilingual UI (Traditional Chinese / English), a built-in AI assistant (natural-language SQL, AI review and tuning advice) and the dbk CLI. Free and open source (MIT), with installers for Windows, macOS and Linux.
- [VS Code Super Mermaid](https://blog.markkulab.net/en/tools/super-mermaid): Super Mermaid is a VS Code extension for beautiful Mermaid diagrams out of the box: auto-colored live preview, mouse pan & zoom, high-res PNG / SVG export, 21 templates and multiple themes. Free and open source (MIT).
- [React Super Mermaid](https://blog.markkulab.net/en/tools/react-super-mermaid): react-super-mermaid is an open-source React component library: render beautiful Mermaid diagrams with a single <MermaidViewer>, with built-in colorful / sketch themes, pan & zoom, in-diagram search, and high-res SVG / PNG export. Lightweight, SSR-safe, fully typed. Free and open source (MIT).
- [Jira / Confluence Super Mermaid](https://blog.markkulab.net/en/tools/jira-super-mermaid): An Atlassian Forge app: write Mermaid syntax directly inside a Jira issue or a Confluence page and get flowcharts, sequence diagrams, state machines and Gantt charts. 11 diagram types, SVG / PNG export, light and dark themes, full CJK support. Runs on Atlassian: your diagrams live in your own site and the app calls no third-party service. Free, coming soon to the Atlassian Marketplace.
- [Mermaid Live Preview](https://blog.markkulab.net/en/tools/mermaid-preview): Write Mermaid in your browser, see it render instantly, and share the whole diagram as a single link. No sign-up, nothing uploaded to a server, and mermaid.live share links work as-is.
- [React Intl Phone Number](https://blog.markkulab.net/en/tools/react-intl-phone-number): react-intl-phone-number is an open-source React component: framework-agnostic and antd-free, with E.164 in/out, a searchable flag / country-code dropdown, configurable validation levels (strict / mobile-strict / loose), themeable CSS, and i18n — phone logic powered by google-libphonenumber. Lightweight and fully typed. Free and open source (MIT).
- [Uptime Kuma Cluster](https://blog.markkulab.net/en/tools/uptime-kuma-cluster): Turn single-node Uptime Kuma into a highly available cluster: OpenResty + Lua smart load balancing, shared MariaDB state, health checks and automatic failover, plus cluster-management REST APIs. One Docker Compose command to start. Free and open source (MIT).
- [Special Education](https://blog.markkulab.net/en/education): Learning materials crafted for special education students

### Daily podcasts

- [Mark's Tech Insights — Daily AI News](https://blog.markkulab.net/en/category/tech-news): Daily curated AI and tech trends. Catch the latest developments via audio summaries — covering AI applications, software architecture, DevOps, and engineering practice. — RSS: https://blog.markkulab.net/feed.xml
- [AI股市蝦聊](https://blog.markkulab.net/en/category/ai-stock-chat): Every trading day, an AI-analyzed take on the Taiwan stock market, delivered as a two-host conversation covering the session and the next-day outlook. — RSS: https://blog.markkulab.net/ai-stock-chat/feed.xml
- [開源好物週報](https://blog.markkulab.net/en/category/open-source-weekly): A weekly two-host pick of free open-source tools surfaced from real Hacker News, GitHub, and Reddit buzz — what pain they solve and the fastest way to get started. — RSS: https://blog.markkulab.net/open-source-weekly/feed.xml

### Newsletter

[Subscribe to the newsletter](https://blog.markkulab.net/en/subscribe) — Be the first to know about new posts. No spam, unsubscribe anytime.
