Deploying Full-Stack Vue to Production
Docker Compose for Vue + Fastify, nginx reverse proxy, SSL with Let's Encrypt, CI/CD.
Deploying Full-Stack Vue to Production is a free Vue Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Vue Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
The Production Topology
A full-stack Vue + Fastify app in production typically runs as two containers: an nginx container serving the built Vue static files, and a Fastify API container. nginx also reverse-proxies /api to the API.
Building the Vue App
Build the SPA with Vite to produce optimized static assets in dist. This is what nginx will serve.
# package.json script
npm run build
# produces dist/ with hashed JS/CSS and index.htmlVue Dockerfile (multi-stage)
Use a multi-stage Docker build: build with Node, then copy the static output into a small nginx image. The final image contains only static files and nginx.
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.confnginx.conf: SPA + API Proxy
The nginx config serves the SPA (with a try_files fallback to index.html for client routing) and proxies /api to the Fastify container by its service name.
server {
listen 80;
root /usr/share/nginx/html;
location / {
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://fastify-api:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}Fastify Dockerfile
The API image installs production dependencies and runs the server. Bind to 0.0.0.0 so it is reachable from other containers.
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]docker-compose.yml
docker-compose wires the two services on a shared network. The proxy target fastify-api in nginx.conf matches the service name here.
services:
vue-nginx:
build: ./frontend
ports:
- '80:80'
- '443:443'
depends_on:
- fastify-api
fastify-api:
build: ./backend
environment:
- JWT_SECRET=${JWT_SECRET}
expose:
- '3000'Service Discovery by Name
Inside the compose network, containers reach each other by service name. nginx proxies to http://fastify-api:3000 — no IPs, no public exposure of the API port.
# only nginx is published to the host (80/443)
# fastify-api uses 'expose', reachable only internallySSL with Certbot
Add HTTPS using Let's Encrypt via Certbot. Mount the certificates into nginx and configure a 443 server block, redirecting HTTP to HTTPS.
server {
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/app.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.com/privkey.pem;
# ... same location blocks as before
}
server {
listen 80;
return 301 https://$host$request_uri;
}Certbot Renewal
Certbot certificates last 90 days. Run a renewal command on a schedule (cron or a sidecar container) and reload nginx so renewed certs take effect.
# cron entry, twice daily
certbot renew --quiet && nginx -s reloadGitHub Actions CI/CD
Automate build and deploy with GitHub Actions: on push to main, build and test, then build/push images or SSH to the server to pull and restart with compose.
name: deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm test
- name: Deploy over SSH
run: ssh $HOST 'cd /app && git pull && docker compose up -d --build'Secrets and Environment
Never bake secrets into images. Pass them via environment variables (compose environment / env_file) sourced from GitHub Actions secrets. Keep JWT_SECRET, DB URLs, etc. out of the repo.
Quick Check
Test your understanding of full-stack deployment.
Recap
You learned production deployment:
- Two services:
vue-nginx(static) andfastify-api nginx.confserves the SPA and proxies/apito the API service namedocker-composewires them; only nginx is published to the host- HTTPS via Certbot with scheduled renewal
- GitHub Actions builds, tests, and deploys; secrets stay in env vars
Frequently asked questions
Is the “Deploying Full-Stack Vue to Production” lesson free?
Yes — the full text of “Deploying Full-Stack Vue to Production” is free to read here on the web, and the Vue Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Vue Academy course, upgrade to CoddyKit PRO.
What will I learn in “Deploying Full-Stack Vue to Production”?
Docker Compose for Vue + Fastify, nginx reverse proxy, SSL with Let's Encrypt, CI/CD. You practise Vue Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Vue Academy?
No prior experience is required. Vue Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Deploying Full-Stack Vue to Production” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Vue Academy lesson?
Yes. Every Vue Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Fastify Backend for Vue SPAs
- JWT Authentication: Login and Refresh
- Authenticated API Calls with Axios Interceptors
- Deploying Full-Stack Vue to Production