Skip to content

swift-serverless/swift-dev-env-vscode-devcontainers-localstack

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

The Ultimate Swift Dev Environment: VS Code, Dev Containers, & LocalStack

Setting up a consistent, cross-platform Swift development environment can be tricky, especially when you need to interact with AWS services. This guide will walk you through creating a powerful, containerized setup using Visual Studio Code, Dev Containers, and LocalStack.

You'll get a fully functional Swift environment that runs inside Docker, complete with debugging, code completion, and a local AWS cloud stack for all your testing needs. Best of all? It works identically on macOS, Linux, and Windows.

LocalStack is a fully functional local AWS cloud stack that allows you to test your cloud applications locally without connecting to the real AWS cloud. It provides a set of APIs that mimic the behavior of AWS services, making it easy to develop and test your applications in a safe and controlled environment.

The Ultimate Swift Dev Environment


Prerequisites

Before we start, make sure you have these tools installed on your system:


The Tutorial: Step-by-Step Setup

Let's build our environment from the ground up.

Step 1: Create Your Project Folder

Open your terminal, create a new directory for your project, and navigate into it.

mkdir SwiftInDevContainer
cd SwiftInDevContainer

Step 2: Open the Project in VS Code

Launch VS Code and open the SwiftInDevContainer folder you just created.

Step 3: Initialize a Swift Project

Open the VS Code integrated terminal and use the Swift Package Manager to create a new executable project.

swift package init --type executable --name SwiftInDevContainer

Step 4: Generate SourceKit-LSP Configuration

For Swift code completion and language features to work, we need to generate a config file.

  1. Open the command palette: Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows/Linux).
  2. Type and select "Swift: Generate SourceKit-LSP Configuration...".

This will create a sourcekit-lsp/config.json file. It should look like this:

{
  "$schema": "https://raw.githubusercontent.com/swiftlang/sourcekit-lsp/refs/heads/release/6.2/config.schema.json"
}

Step 5: Test the Local Build

Let's make sure you can build and run locally first. Try running the debugger. The Swift extension should automatically generate a .vscode/launch.json file for you.

Note: This initial launch.json will contain paths specific to your local machine (like /Users/yourname/Documents...). This is expected! We'll replace this file once we're inside the container.

Step 6: Add Dev Container Configuration

Now, let's tell VS Code how to build our container.

  1. Open the command palette (Cmd+Shift+P / Ctrl+Shift+P).
  2. Type and select "Dev Containers: Add Dev Container Configuration Files...".
  3. Choose "Add configuration to workspace".
  4. Choose "Docker outside of Docker Compose".
  5. Select "latest".
  6. Select the default options
  7. When prompted to add features, select "AWS CLI".
  8. You can also select .github/dependabot.yml if you plan to use it.

This will create a .devcontainer folder with a devcontainer.json and docker-compose.yml file.

Step 7: Modify devcontainer.json

We need to customize this file to install the Swift extension inside the container and configure the LLDB debugger.

Open .devcontainer/devcontainer.json and add the "customizations" block shown below. This will automatically install the Swift extension and tell the debugger where to find the correct library inside the container.

// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/docker-outside-of-docker-compose
{
    "name": "Docker from Docker Compose",
    "dockerComposeFile": "docker-compose.yml",
    "service": "app",
    "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",

    // Use this environment variable if you need to bind mount your local source code into a new container.
    "remoteEnv": {
        "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}"
    },

    "features": {
        "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {
            "version": "latest",
            "enableNonRootDocker": "true",
            "moby": "true"
        },
        "ghcr.io/devcontainers/features/aws-cli:1": {}
    },

    // Add this customizations block
    "customizations": {
        // Configure properties specific to VS Code.
        "vscode": {
            // Set *default* container specific settings.json values on container create.
            "settings": {
                "lldb.library": "/usr/lib/liblldb.so"
            },
            // Add the IDs of extensions you want installed when the container is created.
            "extensions": [
                "swiftlang.swift-vscode"
            ]
        }
    }
    // ... (rest of the file)
}

Step 8: Modify docker-compose.yml

Next, open .devcontainer/docker-compose.yml. We need to:

  1. Add the LocalStack service.
  2. Make our app service depend on LocalStack.
  3. Add environment variables so our Swift app can find LocalStack.
  4. Enable ptrace for the debugger to work.

Your final docker-compose.yml should look like this:

version: '3'

services:
  app:
    build: 
      context: .
      dockerfile: Dockerfile

    volumes:
      # Forwards the local Docker socket to the container.
      - /var/run/docker.sock:/var/run/docker-host.sock 
      # Update this to wherever you want VS Code to mount the folder of your project
      - ../..:/workspaces:cached

    # Overrides default command so things don't shut down after the process ends.
    entrypoint: /usr/local/share/docker-init.sh
    
    # Add dependency on localstack
    depends_on:
      - localstack
      
    # Add environment variables for AWS SDK
    environment:
      - LOCALSTACK_ENDPOINT=http://localstack:4566
      - AWS_ACCESS_KEY_ID=test
      - AWS_SECRET_ACCESS_KEY=test
      - AWS_REGION=us-east-1
    command: sleep infinity 

    # Uncomment the next four lines if you will use a ptrace-based debuggers like C++, Go, and Rust.
    cap_add:
      - SYS_PTRACE
    security_opt:
      - seccomp:unconfined

    # Use "forwardPorts" in **devcontainer.json** to forward an app port locally. 
    # (Adding the "ports" property to this file will not forward from a Codespace.)

  # Add the localstack service
  localstack:
    image: localstack/localstack

Step 9: Create the Dockerfile

In the same .devcontainer folder, change the Dockerfile content. This file will define our main app container.

We'll use an official Swift image and add make, which is useful for utility scripts.

FROM swift:6.2.0

# Add make installation
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
    && apt-get -y install --no-install-recommends make

Step 10: Rebuild and Reopen in Container

Now for the magic.

  1. Open the command palette (Cmd+Shift+P / Ctrl+Shift+P).
  2. Type and select "Dev Containers: Rebuild and Reopen in Container".

VS Code will now build your Dockerfile, start the docker-compose services (your Swift app and LocalStack), and reload the window, connecting you inside the app container. You'll know it worked when the bottom-left corner of VS Code shows "Dev Container: ...".

Step 11: Re-configure and Run the Debugger

Remember that old launch.json from Step 5? It's pointing to your local machine's paths, which don't exist inside the container. Let's fix it.

  1. You can delete the old .vscode/launch.json file if you want, or just let this next step overwrite it.
  2. Select the Package.swift file in the Explorer to make it the active file.
  3. Open the command palette (Cmd+Shift+P / Ctrl+Shift+P).
  4. Type and select "Swift: Generate Launch Configuration".

This will create a new launch.json with the correct container paths (e.g., /workspaces/SwiftInDevContainer/.build/debug/SwiftInDevContainer).

Now, you can start Debugging. The project will build inside the container, and the "Hello, world!" output will appear in your debug console.

Step 12: Interact with LocalStack

Let's prove our container can talk to the LocalStack container. We'll add Soto, the Swift SDK for AWS, to query DynamoDB.

First, update your Package.swift to include soto-dynamodb:

// swift-tools-version: 6.2

import PackageDescription

let package = Package(
    name: "SwiftInDevContainer",
    dependencies: [
        .package(url: "https://github.com/soto-project/soto.git", from: "7.0.0"),
    ],
    targets: [
        .executableTarget(
            name: "SwiftInDevContainer",
            dependencies: [
                .product(name: "SotoDynamoDB", package: "soto"),
            ]
        ),
    ]
)

Next, update Sources/SwiftInDevContainer/SwiftInDevContainer.swift to list DynamoDB tables:

import SotoDynamoDB

@main
struct SwiftInDevContainer {
    static func main() async throws {

        // Use the environment variables we set in docker-compose.yml
        let awsClient = AWSClient()
        // Point the client at our LocalStack container
        let dynamoDB = DynamoDB(client: awsClient, endpoint: "http://localstack:4566")
        
        print("List tables")
        let result = try await dynamoDB.listTables()
        print("result: \(result)")
        
        try await awsClient.shutdown()
    }
}

Run the debugger again. VS Code will resolve the new package and run the code. You should see this output, as we haven't created any tables yet:

List tables
result: ListTablesOutput(lastEvaluatedTableName: nil, tableNames: Optional([]))

Step 13: Create a DynamoDB Table

Let's create a table using the AWS CLI (which we installed in our container) and make.

Create a Makefile in the root of your project:

localstack_create_dynamo_db_table:
	aws --endpoint-url=http://localstack:4566 dynamodb create-table \
		--table-name Breeze \
		--attribute-definitions AttributeName=itemKey,AttributeType=S \
		--key-schema AttributeName=itemKey,KeyType=HASH \
		--billing-mode PAY_PER_REQUEST \
		--region us-east-1

Now, in the VS Code terminal (which is inside the container), run the command:

make localstack_create_dynamo_db_table

This tells the AWS CLI to connect to our localstack service and create a table named "Breeze".

Run your Swift app one more time (F5). This time, the output will show your new table!

List tables
result: ListTablesOutput(lastEvaluatedTableName: nil, tableNames: Optional(["Breeze"]))

You're All Set!

Congratulations! You now have a fully containerized, cross-platform Swift development environment. You can build, debug, and test your Swift applications that interact with AWS services, all without needing a real AWS account or installing Swift on your host machine.

If you're interested in taking this further and building Swift serverless applications with AWS Lambda and DynamoDB, check out the Breeze project.

Happy coding!

Don't forget to check out my blog: www.andrea-scuderi.com

About

The Ultimate Swift Dev Environment: VS Code, Dev Containers, & LocalStack

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published