Deploy a Swift REST API to Cloud Run

Build a Bookshelf REST API in Swift and deploy it to Google Cloud Run.

Introduction

30 Second Summary

Swift isn't just for building iPhone apps. It can power the servers behind them too.

In this project, you will build a REST API in Swift using the Vapor framework, package it into a container image using Apple's native container CLI, and deploy it live to Google Cloud Run.

What You'll Build

A "Bookshelf" REST API that accepts and returns JSON, uses in-memory storage, and runs as a serverless container on Google Cloud Run.

By the end of this project, you'll have:

  • A Vapor project with multiple REST endpoints that handle JSON encoding and decoding using Swift's Codable protocol.
  • A multi-stage Dockerfile optimized for production that produces a lean container image.
  • A container image built and tested locally with Apple's native container CLI.
  • A live, publicly accessible API deployed to Google Cloud Run. Plus a secret mission to add a search endpoint that filters books by author or title.

Are there any prerequisites?

You need macOS 26 (Tahoe) on Apple silicon, Homebrew, Swift, and Docker installed. The project will walk you through installing the Vapor toolbox, Apple's container CLI, and the Google Cloud CLI.

Set Up Vapor and Create Your Project

Before you can build a REST API, you need a framework that handles the heavy lifting of HTTP requests, routing, and JSON encoding. Vapor is the most popular server-side Swift framework, with over 26,000 GitHub stars and a CLI toolbox that scaffolds projects for you. In this step, you'll install Vapor and create the foundation for your Bookshelf API.

In This Step, Get Ready To:
  • Install the Vapor toolbox using Homebrew.
  • Scaffold a new Vapor project called Bookshelf.
  • Run and test the default server locally.
Install the Vapor Toolbox

The Vapor toolbox is a command-line tool that creates new projects, generates boilerplate code, and provides helpful utilities. You can install it through Homebrew.

  • Open your terminal and run the following command to install Vapor.
brew install vapor
  • Verify the installation by checking the version.
vapor --version

I see a version number

You're all set. The Vapor toolbox is installed and ready to use.

Command not found

If you see a "command not found" error, Homebrew may not have linked the binary correctly.

  • Try running brew link vapor and then check the version again.
  • Make sure your Homebrew installation is up to date with brew update.

Still stuck?

Help me troubleshoot the Vapor installation.

Why Vapor over Hummingbird?

You might have heard of Hummingbird, another server-side Swift framework. Hummingbird is lighter and faster, but Vapor has a much larger community and better documentation. The official Swift.org getting-started guide also uses Vapor. For a learning project, the ecosystem and tooling matter more than raw performance.

Create a New Vapor Project

The Vapor toolbox includes a vapor new command that generates a complete project structure with routing, configuration, and dependency management already wired up.

  • Navigate to your preferred working directory in the terminal.
  • Run the following command to scaffold a new project.
vapor new Bookshelf

The CLI will ask you about optional features like Fluent (database ORM) and Leaf (templating engine).

  • Answer no to all optional features. You only need the basic REST API template for this project.
  • Navigate into the new project directory.
cd Bookshelf

What is Fluent?

Fluent is Vapor's ORM (Object-Relational Mapping) for working with databases. You're skipping it because your Bookshelf API will use simple in-memory storage. This keeps the project focused on REST APIs and containerization. If you want to add a real database later, Fluent is the natural next step.

Open and Explore the Project

Vapor generated several files for you. Understanding the project structure now will make the next step much easier when you start writing your own routes.

  • Open the project in your editor.

VS Code

code .

Xcode

open Package.swift

Take a moment to look at the key files Vapor created for you.

  • Package.swift defines your project's dependencies, including the Vapor framework itself.
  • Sources/App/configure.swift is where you configure the application, including server settings and middleware.
  • Sources/App/routes.swift is where you define your API endpoints. This is the file you'll edit the most.

Now let's make sure the server runs correctly.

  • Start the server with the following command.
swift run

What does this command do?

swift run compiles your Swift project and starts the Vapor server. The first run takes a while because it downloads and compiles all dependencies. Subsequent runs are much faster.

The server starts on http://127.0.0.1:8080 by default. Vapor includes a sample route you can use to verify everything is working.

  • Open a new terminal tab and test the default route.
curl http://localhost:8080/hello

What should I see?

You should see Hello, world! printed in your terminal. This confirms Vapor is running and responding to HTTP requests.

  • Go back to the terminal tab running the server and press Ctrl+C to stop it.

Your Vapor project is up and running. Next up, you'll replace the default route with your own Bookshelf API endpoints that handle JSON with GET and POST requests.

Build Your Bookshelf API

Your Vapor project is scaffolded and running. Now it is time to turn it into something useful. In this step, you will create REST endpoints that accept and return JSON, teaching you how Swift structs, the Codable protocol, and Vapor's routing system work together to handle HTTP requests.

In This Step, Get Ready To:
  • Define a Book data model using a Swift struct and Vapor's Content protocol.
  • Create GET and POST routes that handle JSON encoding and decoding.
  • Test your API locally with curl to confirm everything works end-to-end.
Define Your Data Model

Every REST API needs a data structure to represent the resources it manages. In Swift, you define these structures using structs. Vapor provides a protocol called Content that wraps Swift's Codable protocol. When your struct conforms to Content, Vapor can automatically convert it to and from JSON.

  • Open Sources/App/routes.swift in your editor.
  • Add a Book struct above the routes function with the following code:
import Vapor

struct Book: Content {
    let id: UUID
    let title: String
    let author: String
}

// In-memory storage for books
var books: [Book] = []

What does this code do?

The Book struct defines the shape of your data. Each book has three properties: an id (a unique identifier), a title, and an author. The let keyword means these properties are immutable once set.

Conforming to Content tells Vapor this struct can be automatically encoded to JSON (for responses) and decoded from JSON (for requests). The books array stores books in memory, so data resets every time the server restarts.

Create GET and POST Routes

Your API needs three endpoints: one to list all books, one to add a new book, and one to retrieve a specific book by its ID. Vapor uses closures to define what happens when each route is hit.

  • Replace the existing routes function in routes.swift with the following code:
func routes(_ app: Application) throws {
    // GET /books - Return all books as JSON
    app.get("books") { req in
        return books
    }

    // POST /books - Add a new book from JSON request body
    app.post("books") { req -> Book in
        let book = try req.content.decode(Book.self)
        books.append(book)
        return book
    }

    // GET /books/:id - Find a specific book by UUID
    app.get("books", ":id") { req -> Book in
        guard let idString = req.parameters.get("id"),
              let id = UUID(uuidString: idString) else {
            throw Abort(.badRequest, reason: "Invalid book ID.")
        }
        guard let book = books.first(where: { $0.id == id }) else {
            throw Abort(.notFound, reason: "Book not found.")
        }
        return book
    }
}

What does this code do?

The GET /books route returns the entire books array. Because Book conforms to Content, Vapor automatically serializes the array into a JSON response.

The POST /books route uses req.content.decode(Book.self) to parse the incoming JSON body into a Book struct. It then appends the book to the array and returns it as confirmation.

The GET /books/:id route extracts the id parameter from the URL and searches the array using first(where:). If the ID is invalid or no book matches, it throws an appropriate HTTP error.

Test Your API Locally

Before containerizing anything, you should verify your endpoints work correctly. You will use curl to send HTTP requests to your local server and inspect the JSON responses.

  • Start the server with swift run.
swift run

Wait for the server to start. You should see output indicating it is listening on http://127.0.0.1:8080.

  • Open a new terminal window and add a book with a POST request.
curl -X POST http://localhost:8080/books \
  -H "Content-Type: application/json" \
  -d '{"id": "550e8400-e29b-41d4-a716-446655440000", "title": "The Swift Programming Language", "author": "Apple"}'

What should I see?

The server should return the book you just created as JSON, with the same id, title, and author fields you sent.

  • Retrieve all books to confirm it was stored.
curl http://localhost:8080/books

You should see a JSON array containing the book you just added.

  • Retrieve the specific book by its UUID.
curl http://localhost:8080/books/550e8400-e29b-41d4-a716-446655440000

This should return just the single book object matching that ID.

  • Stop the server by pressing Ctrl+C in the terminal where it is running.

Getting a decoding error on POST?

Make sure your JSON keys exactly match the property names in your Book struct: id, title, and author. The id field must be a valid UUID string. Also verify you included the -H "Content-Type: application/json" header in your curl command. Help me debug my Vapor POST request.

Your API is working locally and handling JSON like a pro. Next up, you will configure your app for containerized environments and write a production Dockerfile.

Prepare Your App for Cloud Deployment

Your Bookshelf API works locally, but it is not ready for the cloud yet. Right now, Vapor only listens for connections from your own machine. Cloud Run needs to connect to your app from outside the container, and it needs to tell your app which port to use. In this step, you will configure Vapor for containerized environments and write a production Dockerfile that produces a lean, deployable image.

In This Step, Get Ready To:
  • Configure Vapor to listen on all network interfaces and read the PORT environment variable.
  • Write a multi-stage Dockerfile that compiles your Swift app and packages it into a small production image.
  • Create a .dockerignore file to keep your build context clean.
Configure Vapor for Containers

By default, Vapor listens on 127.0.0.1, which means it only accepts connections from the same machine. Inside a container, your app runs in its own isolated environment. The container runtime and cloud platform need to reach your app from the outside, so you need to listen on 0.0.0.0 (all network interfaces) instead.

Cloud Run also assigns a port dynamically through the PORT environment variable. Your app needs to read that variable and use it.

  • Open Sources/App/configure.swift in your editor.
  • Add the following lines inside the configure function, before any existing code:
app.http.server.configuration.hostname = "0.0.0.0"
if let portString = Environment.get("PORT"), let port = Int(portString) {
    app.http.server.configuration.port = port
}

What does this code do?

The first line tells Vapor to accept connections from any network interface, not just localhost. The if let block uses Swift's optional binding to safely read the PORT environment variable. If the variable exists and can be converted to an integer, Vapor uses that port. If it does not exist (like when running locally), Vapor falls back to its default port 8080.

  • Save the file.
  • Run swift run to confirm the server still starts without errors.
  • Stop the server with Ctrl+C.
Write a Multi-Stage Dockerfile

A Dockerfile is a recipe that tells a container tool how to build your application image. A multi-stage Dockerfile splits the process into two phases: one stage compiles your code with the full Swift toolchain, and a second stage copies only the compiled binary into a minimal runtime image. This keeps your production image small (under 100 MB) instead of shipping the entire 2+ GB Swift compiler.

  • Create a new file called Dockerfile in the root of your Bookshelf project directory.
  • Add the following content:
# Stage 1: Build the Swift application
FROM swift:6.1-jammy AS builder

WORKDIR /app

# Copy dependency manifest first to cache resolved dependencies
COPY Package.swift Package.resolved ./
RUN swift package resolve

# Copy the rest of the source code and build in release mode
COPY . .
RUN swift build -c release --static-swift-stdlib

# Stage 2: Create a minimal runtime image
FROM ubuntu:22.04

RUN apt-get update && apt-get install -y \
    libcurl4 \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Copy only the compiled binary from the builder stage
COPY --from=builder /app/.build/release/App .

EXPOSE 8080

CMD ["./App"]

What does this Dockerfile do?

Stage 1 uses the swift:6.1-jammy image (based on Ubuntu 22.04) to compile your Vapor app. The --static-swift-stdlib flag statically links the Swift standard library into the binary, so the runtime image does not need Swift installed at all.

Stage 2 starts fresh from a minimal ubuntu:22.04 image and copies only the compiled binary. The result is a production image that is a fraction of the size of the build image.

Why copy Package.swift first?

Container builds cache each step. By copying the dependency manifest and resolving packages before copying your source code, you avoid re-downloading all dependencies every time you change a line of code. The dependency resolution step only re-runs when Package.swift or Package.resolved changes.

Create a .dockerignore File

When you build a container image, the tool sends your entire project directory (the "build context") to the build process. Files like your .build/ directory and .git/ history can be hundreds of megabytes. A .dockerignore file tells the build process to skip these unnecessary files, speeding up the build.

  • Create a new file called .dockerignore in the root of your project directory.
  • Add the following entries:
.build/
.swiftpm/
*.xcodeproj/
*.xcworkspace/
.git/

What does this file do?

Each line is a pattern that gets excluded from the build context. The .build/ directory contains your local Swift compilation artifacts. The .swiftpm/ directory holds local package manager state. The Xcode project files and Git history are not needed inside the container either.

Your app is now fully configured for containerized deployment. You have a Vapor server that listens on the right host and port, a Dockerfile that produces a lean production image, and a .dockerignore that keeps the build fast. Next up, you will use Apple's container CLI to build this image and run it locally before deploying to the cloud.

Build and Run with Apple Container

Your API code is ready and your Dockerfile is written. Now you need to actually build a container image and verify it works before pushing anything to the cloud. Apple's container CLI is a native macOS tool that builds and runs OCI-compatible container images without needing Docker Desktop. Each container runs in its own lightweight virtual machine, giving you strong isolation right on your Mac.

In This Step, Get Ready To:
  • Install and start the Apple container CLI on your Mac.
  • Build a multiplatform container image from your Dockerfile.
  • Run and test your containerized API locally.
Install and Start Apple Container

Apple's container CLI is distributed as a signed installer package from GitHub. It installs under /usr/local and requires macOS 26 (Tahoe) on Apple silicon.

  • Open the GitHub releases page at https://github.com/apple/container/releases.
  • Download the latest installer package (the current version is v0.11.0).
  • Run the installer package to install the CLI.
  • Start the container service by running the following command in your terminal:
container system start

What does this command do?

This starts the background service that manages container virtual machines on your Mac. Without it, you cannot build or run containers. If prompted to install a Linux kernel, accept the default recommendation.

  • Verify the service is running:
container list --all

You should see an empty list with column headers. This confirms the container service is up and ready.

Why Apple Container Instead of Docker?

Docker Desktop on macOS runs all containers inside a single shared Linux VM. Apple's container CLI takes a different approach. Each container gets its own lightweight VM, providing stronger isolation.

The images it produces are OCI-compatible, meaning they work with any container registry and any cloud platform that runs containers. You get native macOS tooling with full industry compatibility.

Having Trouble Starting the Service?

Make sure you are running macOS 26 (Tahoe) on Apple silicon. The container CLI does not support Intel Macs or earlier macOS versions. Help me troubleshoot Apple Container installation issues.

Build Your Container Image

Google Cloud Run runs containers on amd64 architecture, but your Mac uses arm64. You need to build for both architectures so the image runs locally on your Mac and in the cloud. Apple's container CLI handles this with a single command, using Rosetta to cross-compile the amd64 variant.

  • Make sure you are in your Bookshelf project root directory.
  • Run the following command to build a multiplatform image:
container build --tag bookshelf-api --arch arm64 --arch amd64 --file Dockerfile .

What does this command do?

This reads your Dockerfile and builds two image variants. The --arch arm64 variant runs natively on your Apple silicon Mac. The --arch amd64 variant is what Cloud Run needs. Both are bundled into a single multiplatform image tagged bookshelf-api.

This build will take a few minutes. Swift is compiling your entire project in release mode inside the container, then copying just the binary into a minimal runtime image.

  • Once the build finishes, verify your image was created:
container image list

You should see bookshelf-api in the output list. The image should be well under 100 MB thanks to your multi-stage Dockerfile.

Test Your Container Locally

Before deploying to the cloud, you should always verify your container works locally. This catches configuration issues like wrong ports or missing environment variables before they become harder to debug in a remote environment.

  • Start your container in detached mode with port forwarding:
container run --name bookshelf --detach --rm -p 127.0.0.1:8080:8080 bookshelf-api

What does this command do?

  • --name bookshelf gives the container a friendly name so you can reference it later.
  • --detach runs it in the background so you get your terminal back.
  • --rm automatically removes the container when it stops.
  • -p 127.0.0.1:8080:8080 forwards port 8080 on your Mac to port 8080 inside the container.
  • Test that the API responds:
curl http://127.0.0.1:8080/books

You should see an empty JSON array [] since no books have been added yet.

  • Add a book with a POST request to verify the full round trip:
curl -X POST http://127.0.0.1:8080/books \
  -H "Content-Type: application/json" \
  -d '{"id": "550e8400-e29b-41d4-a716-446655440000", "title": "The Swift Programming Language", "author": "Apple"}'
  • Retrieve all books to confirm it was stored:
curl http://127.0.0.1:8080/books

You should see a JSON array containing the book you just added.

  • Stop the container when you are done testing:
container stop bookshelf

Container Starts But curl Returns Connection Refused?

This usually means your Vapor app is still listening on 127.0.0.1 instead of 0.0.0.0. Go back to your configure.swift file and make sure you set app.http.server.configuration.hostname = "0.0.0.0". Then rebuild the image and try again. Help me debug my containerized Vapor app.

Your API is running inside a container and responding to requests. The image is multiplatform, OCI-compatible, and ready to be pushed to a cloud registry. Next up, you will deploy this container to Google Cloud Run and make your API publicly accessible.

Deploy to Google Cloud Run

Your API runs perfectly in a local container. Now it is time to make it accessible to the world. Google Cloud Run is a serverless platform that takes a container image and runs it for you, scaling to zero when nobody is using it. In this step, you will push your image to Google's Artifact Registry and deploy it as a live, publicly accessible API.

In This Step, Get Ready To:
  • Set up a Google Cloud account and install the gcloud CLI.
  • Push your container image to Artifact Registry.
  • Deploy your Bookshelf API to Cloud Run and test it live.
Set Up Your GCP Account and CLI

Before you can deploy anything, you need a Google Cloud project and the gcloud CLI installed on your machine. Google Cloud gives new accounts $300 in free credits for 90 days, and Cloud Run includes 2 million free requests per month.

brew install google-cloud-sdk
  • Initialize the CLI and log in to your Google account.
gcloud init

During initialization, select the option to create a new project. Give it a name like swift-bookshelf. Google will assign your project a unique project ID. Take note of this ID because you will need it throughout the rest of this step.

  • Enable the APIs that Cloud Run and Artifact Registry need.
gcloud services enable artifactregistry.googleapis.com run.googleapis.com

What does this command do?

Google Cloud disables most services by default. This command turns on two services: Artifact Registry (where your container images are stored) and Cloud Run (where your container runs as a serverless service).

  • Create an Artifact Registry repository to store your container images.
gcloud artifacts repositories create swift-apps --repository-format=docker --location=us-central1

What is Artifact Registry?

Artifact Registry is Google Cloud's container image storage service. Think of it like a private library for your container images. Cloud Run pulls images from here when it deploys your service.

Getting a Permissions or Billing Error?

If you see errors about billing not being enabled, go to the Google Cloud Console and link a billing account to your project. The free credits cover everything in this project. Help me troubleshoot my GCP setup.

Push Your Image to Artifact Registry

Your container image currently exists only on your local machine. To deploy it to Cloud Run, you need to push it to Artifact Registry. The Apple container CLI does not support Docker credential helpers yet, so you will authenticate using a short-lived OAuth2 token from gcloud.

  • Authenticate the Apple container CLI with Artifact Registry.
gcloud auth print-access-token | container registry login --username oauth2accesstoken --password-stdin us-central1-docker.pkg.dev

What does this command do?

This command generates a temporary access token from your Google Cloud account and pipes it into the container CLI as a password. The token is short-lived, so if you take a break and come back later, you may need to run this command again.

Is the --password-stdin Flag Not Recognized?

The --password-stdin flag name has not been verified across all versions of the Apple container CLI. If the command fails, run container registry login --help to check the correct flag name for piping a password. Help me fix this authentication command.

  • Tag your local image with the full Artifact Registry path.
container image tag bookshelf-api us-central1-docker.pkg.dev/[[PROJECT_ID="your GCP project ID"]]/swift-apps/bookshelf-api:latest

What does this command do?

Tagging gives your image a new name that includes the full registry path. This tells the container CLI where to push the image. Replace the project ID with the one Google assigned when you created your project.

  • Push the image to Artifact Registry.
container image push us-central1-docker.pkg.dev/[[PROJECT_ID="your GCP project ID"]]/swift-apps/bookshelf-api:latest

This upload may take a few minutes depending on your internet connection. The container CLI pushes the amd64 variant of your image, which is the architecture Cloud Run needs.

Deploy to Cloud Run and Test Your Live API

Your image is now stored in Artifact Registry. The final step is to tell Cloud Run to pull that image and run it as a publicly accessible service.

  • Deploy your API to Cloud Run.
gcloud run deploy bookshelf-api \
  --image us-central1-docker.pkg.dev/[[PROJECT_ID="your GCP project ID"]]/swift-apps/bookshelf-api:latest \
  --region us-central1 \
  --platform managed \
  --allow-unauthenticated \
  --port 8080 \
  --memory 512Mi

What do these flags mean?

  • --platform managed tells GCP to fully manage the infrastructure for you.
  • --allow-unauthenticated makes your API publicly accessible without requiring authentication.
  • --port 8080 tells Cloud Run which port your app listens on. Cloud Run sets the PORT environment variable to this value, which your Vapor configuration reads.
  • --memory 512Mi allocates 512 MB of memory to each container instance.

Wait for the deployment to complete. The CLI outputs your service URL when it finishes. It looks something like https://bookshelf-api-xxxxx-uc.a.run.app.

  • Test your live API by retrieving all books.
curl https://[[CLOUD_RUN_URL="your Cloud Run service URL"]]/books

You should see an empty JSON array [] because this is a fresh container with no books stored yet.

  • Add a book to your live API with a POST request.
curl -X POST https://[[CLOUD_RUN_URL="your Cloud Run service URL"]]/books \
  -H "Content-Type: application/json" \
  -d '{"id": "550e8400-e29b-41d4-a716-446655440000", "title": "The Swift Programming Language", "author": "Apple"}'
  • Retrieve all books again to confirm the book was stored.
curl https://[[CLOUD_RUN_URL="your Cloud Run service URL"]]/books

You should see a JSON array containing the book you just added.

Why does the data reset?

Your API uses in-memory storage, so books are lost whenever the container restarts. Cloud Run scales to zero when idle, which means a new container starts on the next request. For persistent storage, you would add a database using Vapor's ORM called Fluent. That is a great next project to tackle.

Getting a 403 Forbidden or Connection Error?

Make sure you included the --allow-unauthenticated flag in your deploy command. Without it, Cloud Run requires authentication for every request. Also confirm your Vapor app is configured to listen on 0.0.0.0 and reads the PORT environment variable (from Step 3). Help me debug my Cloud Run deployment.

Your Swift API is now live on the internet. You built it with Vapor, containerized it with Apple's native tooling, and deployed it to Google Cloud Run. Next up, check out the Secret Mission to add a search endpoint to your API.

Secret mission

Add a Search Endpoint

Your Bookshelf API can add and retrieve books, but what if someone wants to find a specific book without scrolling through everything? Add a search endpoint that filters books by author or title using URL query parameters.

Before you go

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. Cloud Run scales to zero when idle, so costs are minimal for a learning project. New GCP accounts also have $300 in free credits for 90 days.

Cost warning

Cloud Run charges $0.00001800/vCPU-second and $0.00000200/GiB-second beyond the free tier. The free tier includes 2 million requests/month, 240,000 vCPU-seconds/month, and 450,000 GiB-seconds/month. Artifact Registry is free for up to 500 MB of storage. A single learning project will stay well within these limits, but if you are done experimenting, deleting the resources ensures zero cost.

Resources you used:

  • Cloud Run service: bookshelf-api (serverless container running your API).
  • Artifact Registry repository: swift-apps (stores your container image in GCP).
  • GCP project: The project you created during gcloud init.
  • Apple Container service: The local container runtime started with container system start.
  • Local project files: The Bookshelf directory containing your Vapor project and Dockerfile.

Keep everything running

No action needed. Choose this if you are still actively building or want to keep your live API accessible.

  • Your Cloud Run service scales to zero when idle, so you only pay for actual requests.
  • Artifact Registry stores your container image for future deployments.
  • The Apple Container service continues running locally in the background. You can stop it later with container system stop.

Pause - I'll come back to this later

Delete the Cloud Run service to stop any potential charges, but keep your container image in Artifact Registry so you can redeploy quickly.

  • Delete the Cloud Run service:
gcloud run services delete bookshelf-api --region us-central1
  • Stop the local Apple Container service:
container system stop
  • Keep your local Bookshelf project folder and Artifact Registry image. When you are ready to come back, rebuild and redeploy with gcloud run deploy.

Delete - I don't want to use this again

Remove all project resources from GCP and clean up your local environment.

  • Delete the Cloud Run service:
gcloud run services delete bookshelf-api --region us-central1
  • Delete the Artifact Registry repository:
gcloud artifacts repositories delete swift-apps --location=us-central1
  • Stop the local Apple Container service:
container system stop
  • Optionally, delete your local project folder:
rm -rf ~/path/to/Bookshelf
  • If you no longer need the GCP project itself, you can delete it from the Google Cloud Console under **IAM & Admin** > **Manage Resources**.

Mission accomplished

That's a Wrap!

Nice work! You just built a REST API in Swift, containerized it with Apple's native tooling, and deployed it live to Google Cloud Run. That is a complete server-side Swift workflow from local development to production.

Here's what you learned:

  • Built a Vapor REST API with GET and POST endpoints that handle JSON using Swift's Codable protocol and Vapor's Content wrapper.
  • Used Apple's container CLI to build a multiplatform OCI container image and test it locally on macOS.
  • Deployed a serverless container to Google Cloud Run by pushing your image to Artifact Registry and deploying with gcloud.
  • Added a search endpoint that filters books by author or title using URL query parameters and Swift's filter method.

Ready to quiz yourself?