How to set up stack for a vibe-coded app with Docker?
Learn how to set up an infrastructure for a solid vibe-coded application with Docker, Nuxt, Directus, MySQL, and RustFS for a clean local prototyping.
Yes, a vibe-coded app can be solid. A lot of people hear that phrase and instantly assume ‘AI slop,’ often because they associate it with rushed or poorly projects made without technical knowledge. However, if the app is built on a clean structure and an easy-to-run setup, the code can move quickly while remaining solid, without turning into a big ball of mud later.
In the previous article, I walked through the basic layers of the application and why each one matters while vibe-coding. In this one, I want to show the practical part. This guide shows how to set up a complete vibe-coded app stack with Docker, Nuxt, Directus, MySQL, and RustFS, so your prototype can run locally and then on production.
What to know first before starting?
This guide focuses on wiring the stack together with Docker, not on teaching every tool from scratch. Before following it, you should already understand the basics of Docker, Git, Nuxt, and Directus, or be ready to check their documentation when needed. It is worth knowing:
- What do you need to run Docker?
- How to get started with Nuxt?
- How to get started with Directus?
- How to get started with MySQL?
- How to get started with RustFS?
Before using this stack in a long-running project, check whether newer stable versions are available and update them intentionally. Some images are in alpha stages (RustFS), so they might not be best choice for large production apps, but they are fine enough to start.
What are the layers of a solid application?
Let's assume that we're building a classic web application. That means a frontend for users, a backend for management and logic, a database for data storage, and storage for static files. The goal is to keep every part in its own place so the app stays easier to develop and expand later. If you remember the basic layers from the business article, this is where those layers become real containers instead of just a nice diagram.

In the tech stack I plan to use, Nuxt handles the interface through the client layer and business logic through the server layer. Directus provides the management area and data exchange API endpoints. MySQL stores structured data, and RustFS stores static files. The benefit of this setup is that each part performs a single job. So, when something changes in one node, we don't need to rewrite the whole app.

How to initialize a Docker infrastructure for a vibe-coded app?
I choose the monorepo structure because it keeps the whole codebase in one place while still letting each layer live in its own directory. I create frontend, backend, database, and storage directories in the project root. This makes the project easier to understand.
The next step is creating a docker-compose.yml file, where we can configure the Docker setup. It defines the shape of the application: which services exist, ports they use, volumes they need, and how they connect. It’s the starting point that Docker uses when running the project.
You should also create a .env file to store system configuration and sensitive information. Create this file in the project root and fill it with the required details. Carefully replace the example values with your own ones, with randomly generated keys, secrets, and passwords.
APP_URL="http://localhost:9000"
APP_ENV="local"
DIRECTUS_KEY="19e7d44777ab45f2bff7bf2cfa2b2277"
DIRECTUS_SECRET="ec5c45511ea84300a30acc028c05b602"
DB_HOST="database"
DB_PORT="3306"
DB_USER="johndoe"
DB_PASSWORD="de95a8ac99d848b4b6fdb4d7d354c1a8"
DB_NAME="vibecode"
DB_ROOT_PASSWORD="cc0d6fd235dd4e53b8c5a028b68b8ca6"
S3_KEY="8b29c4079d6144c3bc609f4f081a9f26"
S3_SECRET="57bcaffaaf644de89ae6c225e70ad74a"
S3_BUCKET="default"
S3_REGION="fra1"
S3_ENDPOINT="http://storage:9000"
If you're going to use Git, you can also create a .gitignore file. At this point, you can add .env to the list of ignored files because items with sensitive details should never be in the repo.

How to create a Docker container for Nuxt?
Initialize a Nuxt project in the frontend directory, then create a file named Dockerfile there. This file tells Docker how to set up the environment for Nuxt. It uses Node 24 as the base image, enables Corepack so pnpm is available, copies the cache and configuration files, installs dependencies, and starts the application in development mode.
FROM node:24.18.0-alpine
WORKDIR /app
RUN corepack enable
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
RUN pnpm install
COPY . .
EXPOSE 3000
CMD ["sh", "-c", "pnpm install && exec pnpm dev"]
Now it is time to tell Docker to use the defined environment when starting the frontend container. Create the frontend service in docker-compose.yml, give it a clear name, point it to the Dockerfile, and map the ports. This means that when you access localhost:9000 in your browser, you will see what is on localhost:3000 in the container.
Mount the source code volume so changes in local files are reflected in the Docker container. It's also good to mount node_modules separately so the container keeps its own dependencies instead of conflicting with the host machine. You can use the environment key to define the environment variables that can be resolved automatically from the .env file in the root.
name: coditive-space
services:
frontend:
container_name: coditive-space-frontend
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "9000:3000"
volumes:
- ./frontend:/app:cached
- ./frontend/node_modules:/app/node_modules:delegated
environment:
NUXT_PUBLIC_APP_URL: ${APP_URL}
NUXT_PUBLIC_APP_ENV: ${APP_ENV}
When you start the Docker service, you'll be able to access Nuxt via http://localhost:9000.
How to create a Docker container for Directus?
For Directus, the setup is much simpler because I only need the official image as the base. Since Directus already knows how to run its own app, I do not need to rebuild anything from scratch unless I want to add custom behavior and mechanisms.
FROM directus/directus:12.0.2
In docker-compose.yml, add the backend service, configure it to use that Dockerfile, and map the required ports. Use the environment key to define the database client with variables from the project's .env file. Directus supports several file storage methods, but for this stack, configure it to use local S3. As a result, when uploading items in the CMS, they will land in the RustFS.
backend:
container_name: coditive-space-backend
build:
context: ./backend
dockerfile: Dockerfile
ports:
- "9001:8055"
environment:
KEY: ${DIRECTUS_KEY}
SECRET: ${DIRECTUS_SECRET}
DB_CLIENT: mysql
DB_HOST: ${DB_HOST}
DB_PORT: ${DB_PORT}
DB_USER: ${DB_USER}
DB_PASSWORD: ${DB_PASSWORD}
DB_DATABASE: ${DB_NAME}
STORAGE_LOCATIONS: local
STORAGE_LOCAL_DRIVER: s3
STORAGE_LOCAL_ROOT:
STORAGE_LOCAL_KEY: ${S3_KEY}
STORAGE_LOCAL_SECRET: ${S3_SECRET}
STORAGE_LOCAL_BUCKET: ${S3_BUCKET}
STORAGE_LOCAL_REGION: ${S3_REGION}
STORAGE_LOCAL_ENDPOINT: ${S3_ENDPOINT}
STORAGE_LOCAL_ACL: private
STORAGE_LOCAL_FORCE_PATH_STYLE: "true"
When you start the service, you'll be able to access Directus via http://localhost:9001 URL.
How to create a Docker container for MySQL?
For MySQL, we can use the image directly in docker-compose.yml instead of in a Dockerfile. We do not need to customize the database image just to run MySQL, so using image: mysql:8.4 keeps the setup clear and easy to follow.
We also need to mount a volume to store the database files. That part is important because it keeps the data on your disk. If the container restarts, the data remains. Otherwise, it would be reset every time the container starts. Add the contents of the mounted directory to .gitignore so they are not tracked by GIT.
database:
container_name: coditive-space-database
image: mysql:8.4
ports:
- "9002:3306"
volumes:
- ./database:/var/lib/mysql
environment:
MYSQL_USER: ${DB_USER}
MYSQL_PASSWORD: ${DB_PASSWORD}
MYSQL_DATABASE: ${DB_NAME}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
When you start the service, you'll be able to access MySQL via localhost host with 9002 port.
How to create a Docker container for RustFS?
RustFS is the storage layer, and using the image directly in docker-compose.yml is enough. Tell the service to use the rustfs/rustfs:1.0.0-alpha.90 image, map the ports - first for the API, which you use via an SDK, and second for the management via the browser.
Then mount the file volume so anything that is put into S3 storage lands on your disk instead of resetting when the container stops. In the environment section, you can configure KEY and SECRET values, which you can use to authorize S3 requests in the JS SDK. Add storage volume to a .gitignore file so the contents, like static assets uploaded there, are not tracked by GIT.
storage:
container_name: coditive-space-storage
image: rustfs/rustfs:1.0.0-alpha.90
ports:
- "9003:9000"
- "9004:9001"
volumes:
- ./storage:/data
environment:
RUSTFS_ROOT_USER: ${S3_KEY}
RUSTFS_ROOT_PASSWORD: ${S3_SECRET}
When you start the service, you'll be able to access the RustFS API via http://localhost:9003, and its browser interface via the http://localhost:9004.
How to run the Docker stack?
Once everything is in place, the full stack starts with one command:
docker compose up -d --build
That command builds the custom images, starts the containers in the background, and brings the whole local environment online. After that, each service is available on its own port, so the frontend, backend, database, and storage can all be reached separately from your browser. Here’s how the full stack looks.
name: coditive-space
services:
storage:
container_name: coditive-space-storage
image: rustfs/rustfs:1.0.0-alpha.90
ports:
- "9003:9000"
- "9004:9001"
volumes:
- ./storage:/data
environment:
RUSTFS_ROOT_USER: ${S3_KEY}
RUSTFS_ROOT_PASSWORD: ${S3_SECRET}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/health"]
interval: 5s
timeout: 5s
database:
container_name: coditive-space-database
image: mysql:8.4
ports:
- "9002:3306"
volumes:
- ./database:/var/lib/mysql
environment:
MYSQL_USER: ${DB_USER}
MYSQL_PASSWORD: ${DB_PASSWORD}
MYSQL_DATABASE: ${DB_NAME}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-P", "3306"]
interval: 5s
timeout: 5s
backend:
container_name: coditive-space-backend
build:
context: ./backend
dockerfile: Dockerfile
ports:
- "9001:8055"
environment:
KEY: ${DIRECTUS_KEY}
SECRET: ${DIRECTUS_SECRET}
DB_CLIENT: mysql
DB_HOST: ${DB_HOST}
DB_PORT: ${DB_PORT}
DB_USER: ${DB_USER}
DB_PASSWORD: ${DB_PASSWORD}
DB_DATABASE: ${DB_NAME}
STORAGE_LOCATIONS: local
STORAGE_LOCAL_DRIVER: s3
STORAGE_LOCAL_ROOT:
STORAGE_LOCAL_KEY: ${S3_KEY}
STORAGE_LOCAL_SECRET: ${S3_SECRET}
STORAGE_LOCAL_BUCKET: ${S3_BUCKET}
STORAGE_LOCAL_REGION: ${S3_REGION}
STORAGE_LOCAL_ENDPOINT: ${S3_ENDPOINT}
STORAGE_LOCAL_ACL: private
STORAGE_LOCAL_FORCE_PATH_STYLE: "true"
depends_on:
database:
condition: service_healthy
storage:
condition: service_healthy
frontend:
container_name: coditive-space-frontend
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "9000:3000"
volumes:
- ./frontend:/app:cached
- ./frontend/node_modules:/app/node_modules:delegated
environment:
NUXT_PUBLIC_APP_URL: ${APP_URL}
NUXT_PUBLIC_APP_ENV: ${APP_ENV}
depends_on:
database:
condition: service_healthy
storage:
condition: service_healthy
The configuration file also adds healthcheck rules and depends_on conditions. Those are useful because they help Docker understand when MySQL and RustFS are actually ready before Directus and Nuxt try to use them. Without that, services can start in the wrong order.
To confirm the setup works correctly, run the following tests after starting the application:
- Open
http://localhost:9000in a browser. You should see the Nuxt welcome screen. Modify layout and confirm the browser automatically reflects the changes. - Open
http://localhost:9001in a browser. You should see the Directus screen to complete the installation. Once done, you should be able to log in to the panel. - Open
http://localhost:9004in a browser. You should see the RustFS GUI for managing S3 storage. Log in with the credentials you set and try uploading a file. It should land in the defined space on disk.
What are the useful Docker tricks?
Here are a few things that might help you reduce trouble when working with Docker. These are the ones I have often forgotten, especially when I was just getting started with Docker.
How to run commands for the container?
A common thing to miss is that the app runs inside a container. So when running commands for the app, you should run them in the container with docker compose exec command. For example, to install swiperjs package in the frontend container, you should run this command.
docker compose exec frontend pnpm install swiperjs
How to access the container terminal?
Sometimes, you may need to access the terminal of a specific container for longer. Maybe to search through files there, check logs, or run other tools directly in the container, as you would do on your host machine. You can get into the container with a command like this.
docker compose exec frontend sh
How to reach the container address?
A common thing with Docker is confusing a container's network context with the host. For example, if you want to access CMS, you use http://localhost:9001 in your browser, it works. Then, inside the app, you use the same URL to query API endpoints, and it does not work.
The key is that localhost means different things depending on where you are. On your host, localhost:9001 points to the host itself, so it may work in the browser. But inside a Docker container, localhost refers to that container, so the same URL won’t work there.
When container needs to access another service, it should use its name and internal port.
fetch(`http://backend:8055/api/posts`)
What is the takeaway?
If you want a vibe-coded app to stay solid, you need a structure that is easy to run and understand. Docker Compose gives you that structure by keeping the frontend, backend, database, and storage in separate containers while still letting them work together as one system. That makes the whole stack easier to develop and much less painful to maintain.
Thank you so much for joining me in today ❤️ Your support means a lot to me, and it keeps me motivated to create more content. If you enjoyed this video, please consider subscribing to the channel and giving it a thumbs-up - it really helps spread the word. And if you need developers who truly care about quality and delivering great results, visit my company’s site: coditive.com or contact me with the form below. Thanks again and see you next time!

Looking For a Developer Who
Truly Cares About Your Business?
My team and I provide expert consultations, top-notch coding, and comprehensive audits to elevate your success.
Feedback
How satisfied you are after reading this article?




